Try to include the database
continuous-integration/drone/push Build was killed
Details
continuous-integration/drone/push Build was killed
Details
parent
dde5af1443
commit
d7209f4ca2
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* phpMyAdmin sample configuration, you can use it as base for
|
||||
* manual configuration. For easier setup you can use setup/
|
||||
*
|
||||
* All directives are explained in documentation in the doc/ folder
|
||||
* or at <https://docs.phpmyadmin.net/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This is needed for cookie based authentication to encrypt password in
|
||||
* cookie. Needs to be 32 chars long.
|
||||
*/
|
||||
$cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */
|
||||
|
||||
/**
|
||||
* Servers configuration
|
||||
*/
|
||||
$i = 0;
|
||||
|
||||
/**
|
||||
* First server
|
||||
*/
|
||||
$i++;
|
||||
/* Authentication type */
|
||||
$cfg['Servers'][$i]['auth_type'] = 'cookie';
|
||||
/* Server parameters */
|
||||
$cfg['Servers'][$i]['host'] = 'localhost';
|
||||
$cfg['Servers'][$i]['compress'] = false;
|
||||
$cfg['Servers'][$i]['AllowNoPassword'] = false;
|
||||
|
||||
/**
|
||||
* phpMyAdmin configuration storage settings.
|
||||
*/
|
||||
|
||||
/* User used to manipulate with storage */
|
||||
// $cfg['Servers'][$i]['controlhost'] = '';
|
||||
// $cfg['Servers'][$i]['controlport'] = '';
|
||||
// $cfg['Servers'][$i]['controluser'] = 'pma';
|
||||
// $cfg['Servers'][$i]['controlpass'] = 'pmapass';
|
||||
|
||||
/* Storage database and tables */
|
||||
// $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
|
||||
// $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
|
||||
// $cfg['Servers'][$i]['relation'] = 'pma__relation';
|
||||
// $cfg['Servers'][$i]['table_info'] = 'pma__table_info';
|
||||
// $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
|
||||
// $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
|
||||
// $cfg['Servers'][$i]['column_info'] = 'pma__column_info';
|
||||
// $cfg['Servers'][$i]['history'] = 'pma__history';
|
||||
// $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
|
||||
// $cfg['Servers'][$i]['tracking'] = 'pma__tracking';
|
||||
// $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
|
||||
// $cfg['Servers'][$i]['recent'] = 'pma__recent';
|
||||
// $cfg['Servers'][$i]['favorite'] = 'pma__favorite';
|
||||
// $cfg['Servers'][$i]['users'] = 'pma__users';
|
||||
// $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
|
||||
// $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
|
||||
// $cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
|
||||
// $cfg['Servers'][$i]['central_columns'] = 'pma__central_columns';
|
||||
// $cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings';
|
||||
// $cfg['Servers'][$i]['export_templates'] = 'pma__export_templates';
|
||||
|
||||
/**
|
||||
* End of servers configuration
|
||||
*/
|
||||
|
||||
/**
|
||||
* Directories for saving/loading files from server
|
||||
*/
|
||||
$cfg['UploadDir'] = '';
|
||||
$cfg['SaveDir'] = '';
|
||||
|
||||
/**
|
||||
* Whether to display icons or text or both icons and text in table row
|
||||
* action segment. Value can be either of 'icons', 'text' or 'both'.
|
||||
* default = 'both'
|
||||
*/
|
||||
//$cfg['RowActionType'] = 'icons';
|
||||
|
||||
/**
|
||||
* Defines whether a user should be displayed a "show all (records)"
|
||||
* button in browse mode or not.
|
||||
* default = false
|
||||
*/
|
||||
//$cfg['ShowAll'] = true;
|
||||
|
||||
/**
|
||||
* Number of rows displayed when browsing a result set. If the result
|
||||
* set contains more rows, "Previous" and "Next".
|
||||
* Possible values: 25, 50, 100, 250, 500
|
||||
* default = 25
|
||||
*/
|
||||
//$cfg['MaxRows'] = 50;
|
||||
|
||||
/**
|
||||
* Disallow editing of binary fields
|
||||
* valid values are:
|
||||
* false allow editing
|
||||
* 'blob' allow editing except for BLOB fields
|
||||
* 'noblob' disallow editing except for BLOB fields
|
||||
* 'all' disallow editing
|
||||
* default = 'blob'
|
||||
*/
|
||||
//$cfg['ProtectBinary'] = false;
|
||||
|
||||
/**
|
||||
* Default language to use, if not browser-defined or user-defined
|
||||
* (you find all languages in the locale folder)
|
||||
* uncomment the desired line:
|
||||
* default = 'en'
|
||||
*/
|
||||
//$cfg['DefaultLang'] = 'en';
|
||||
//$cfg['DefaultLang'] = 'de';
|
||||
|
||||
/**
|
||||
* How many columns should be used for table display of a database?
|
||||
* (a value larger than 1 results in some information being hidden)
|
||||
* default = 1
|
||||
*/
|
||||
//$cfg['PropertiesNumColumns'] = 2;
|
||||
|
||||
/**
|
||||
* Set to true if you want DB-based query history.If false, this utilizes
|
||||
* JS-routines to display query history (lost by window close)
|
||||
*
|
||||
* This requires configuration storage enabled, see above.
|
||||
* default = false
|
||||
*/
|
||||
//$cfg['QueryHistoryDB'] = true;
|
||||
|
||||
/**
|
||||
* When using DB-based query history, how many entries should be kept?
|
||||
* default = 25
|
||||
*/
|
||||
//$cfg['QueryHistoryMax'] = 100;
|
||||
|
||||
/**
|
||||
* Whether or not to query the user before sending the error report to
|
||||
* the phpMyAdmin team when a JavaScript error occurs
|
||||
*
|
||||
* Available options
|
||||
* ('ask' | 'always' | 'never')
|
||||
* default = 'ask'
|
||||
*/
|
||||
//$cfg['SendErrorReports'] = 'always';
|
||||
|
||||
/**
|
||||
* 'URLQueryEncryption' defines whether phpMyAdmin will encrypt sensitive data from the URL query string.
|
||||
* 'URLQueryEncryptionSecretKey' is a 32 bytes long secret key used to encrypt/decrypt the URL query string.
|
||||
*/
|
||||
//$cfg['URLQueryEncryption'] = true;
|
||||
//$cfg['URLQueryEncryptionSecretKey'] = '';
|
||||
|
||||
/**
|
||||
* You can find more configuration options in the documentation
|
||||
* in the doc/ folder or at <https://docs.phpmyadmin.net/>.
|
||||
*/
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
$username="dafldev";
|
||||
$host="89.83.53.34";
|
||||
$password="wrap";
|
||||
$db_name="positiondaflmusic";
|
||||
|
||||
$connect=mysqli_connect($host,$username,$password,$db_name);
|
||||
/*
|
||||
if(!$connect)
|
||||
{
|
||||
print "Connection Failed";
|
||||
}
|
||||
*/
|
||||
|
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
$dns = 'mysql:host=89.83.53.34;dbname=positiondaflmusic';
|
||||
$user = 'dafldev';
|
||||
$password = 'wrap';
|
||||
try{
|
||||
$db = new PDO ($dns, $user, $password);
|
||||
}catch( PDOException $e){
|
||||
$error = $e->getMessage();
|
||||
echo $error;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
|
||||
class distance
|
||||
{
|
||||
public function meters($lat1, $lng1, $lat2, $lng2): float
|
||||
{
|
||||
// Radius of the Earth in meters :
|
||||
$earth_radius = 6378137;
|
||||
// Calculation of the distance between 2 GPS coordinates:
|
||||
$rlo1 = deg2rad($lng1);
|
||||
$rla1 = deg2rad($lat1);
|
||||
$rlo2 = deg2rad($lng2);
|
||||
$rla2 = deg2rad($lat2);
|
||||
$dlo = ($rlo2 - $rlo1) / 2;
|
||||
$dla = ($rla2 - $rla1) / 2;
|
||||
$a = (sin($dla) * sin($dla)) + cos($rla1) * cos($rla2) * (sin($dlo) * sin($dlo));
|
||||
$d = 2 * atan2(sqrt($a), sqrt(1 - $a));
|
||||
// Return the distance in meters between 2 GPS points
|
||||
return round($earth_radius * $d);
|
||||
}
|
||||
}
|
||||
|
||||
$db=0;
|
||||
$connect="";
|
||||
require_once('db.php');
|
||||
include "config.php";
|
||||
$id = $_POST['id'];
|
||||
$query = 'SELECT * FROM gps';
|
||||
$stm = $db->prepare($query);
|
||||
$stm->execute();
|
||||
$row = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||
$lat1=0;
|
||||
$lng1=0;
|
||||
$listUser=[];
|
||||
Foreach ($row as $col) {
|
||||
if (strcmp($col['id'], $id) == 0) {
|
||||
$lat1 = $col['latitude'];
|
||||
$lng1 = $col['longitude'];
|
||||
}
|
||||
}
|
||||
if ($lat1==0 && $lng1==0){
|
||||
print(json_encode("ERROR No user found in the database"));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Foreach ($row as $col) {
|
||||
if (strcmp($col['id'],$id)!=0) {
|
||||
$lat2 = $col['latitude'];
|
||||
$lng2 = $col['longitude'];
|
||||
$userID = $col['id'];
|
||||
$idMusic = $col['idMusic'];
|
||||
$dist = (new distance)->meters($lat1, $lng1, $lat2, $lng2);
|
||||
if ($dist <= 100) {
|
||||
$listUser[] = ['user' => $userID, 'music' => $idMusic]; }
|
||||
}
|
||||
}
|
||||
|
||||
print(json_encode($listUser));
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 8.3 KiB |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,440 @@
|
||||
.. _glossary:
|
||||
|
||||
Glossary
|
||||
========
|
||||
|
||||
From Wikipedia, the free encyclopedia
|
||||
|
||||
.. glossary::
|
||||
|
||||
.htaccess
|
||||
the default name of Apache's directory-level configuration file.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/.htaccess>
|
||||
|
||||
ACL
|
||||
Access Control List
|
||||
|
||||
Blowfish
|
||||
a keyed, symmetric block cipher, designed in 1993 by `Bruce Schneier <https://en.wikipedia.org/wiki/Bruce_Schneier>`_.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Blowfish_(cipher)>
|
||||
|
||||
Browser
|
||||
a software application that enables a user to display and interact with text, images, and other information typically located on a web page at a website on the World Wide Web.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Web_browser>
|
||||
|
||||
bzip2
|
||||
a free software/open-source data compression algorithm and program developed by Julian Seward.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Bzip2>
|
||||
|
||||
CGI
|
||||
Common Gateway Interface is an important World Wide Web technology that
|
||||
enables a client web browser to request data from a program executed on
|
||||
the web server.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Common_Gateway_Interface>
|
||||
|
||||
Changelog
|
||||
a log or record of changes made to a project.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Changelog>
|
||||
|
||||
Client
|
||||
a computer system that accesses a (remote) service on another computer by some kind of network.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Client_(computing)>
|
||||
|
||||
column
|
||||
a set of data values of a particularly simple type, one for each row of the table.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Column_(database)>
|
||||
|
||||
Cookie
|
||||
a packet of information sent by a server to a World Wide Web browser and then sent back by the browser each time it accesses that server.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/HTTP_cookie>
|
||||
|
||||
CSV
|
||||
Comma-separated values
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Comma-separated_values>
|
||||
|
||||
DB
|
||||
look at :term:`Database`
|
||||
|
||||
Database
|
||||
an organized collection of data.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Database>
|
||||
|
||||
Engine
|
||||
look at :term:`Storage Engines`
|
||||
|
||||
PHP extension
|
||||
a PHP module that extends PHP with additional functionality.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Software_extension>
|
||||
|
||||
FAQ
|
||||
Frequently Asked Questions is a list of commonly asked question and their
|
||||
answers.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/FAQ>
|
||||
|
||||
Field
|
||||
one part of divided data/columns.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Field_(computer_science)>
|
||||
|
||||
Foreign key
|
||||
a column or group of columns in a database row that points to a key column
|
||||
or group of columns forming a key of another database row in some
|
||||
(usually different) table.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Foreign_key>
|
||||
|
||||
GD
|
||||
Graphics Library by Thomas Boutell and others for dynamically manipulating images.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/GD_Graphics_Library>
|
||||
|
||||
GD2
|
||||
look at :term:`GD`
|
||||
|
||||
GZip
|
||||
GZip is short for GNU zip, a GNU free software file compression program.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Gzip>
|
||||
|
||||
host
|
||||
any machine connected to a computer network, a node that has a hostname.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Host_(network)>
|
||||
|
||||
hostname
|
||||
the unique name by which a network-attached device is known on a network.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Hostname>
|
||||
|
||||
HTTP
|
||||
Hypertext Transfer Protocol is the primary method used to transfer or
|
||||
convey information on the World Wide Web.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/HyperText_Transfer_Protocol>
|
||||
|
||||
HTTPS
|
||||
a :term:`HTTP`-connection with additional security measures.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/HTTPS>
|
||||
|
||||
IEC
|
||||
International Electrotechnical Commission
|
||||
|
||||
IIS
|
||||
Internet Information Services is a set of internet-based services for
|
||||
servers using Microsoft Windows.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Internet_Information_Services>
|
||||
|
||||
Index
|
||||
a feature that allows quick access to the rows in a table.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Database_index>
|
||||
|
||||
IP
|
||||
"Internet Protocol" is a data-oriented protocol used by source and
|
||||
destination hosts for communicating data across a packet-switched
|
||||
internetwork.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Internet_Protocol>
|
||||
|
||||
IP Address
|
||||
a unique number that devices use in order to identify and communicate with each other on a network utilizing the Internet Protocol standard.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/IP_Address>
|
||||
|
||||
IPv6
|
||||
IPv6 (Internet Protocol version 6) is the latest revision of the
|
||||
Internet Protocol (:term:`IP`), designed to deal with the
|
||||
long-anticipated problem of its predecessor IPv4 running out of addresses.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/IPv6>
|
||||
|
||||
ISAPI
|
||||
Internet Server Application Programming Interface is the API of Internet Information Services (IIS).
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Internet_Server_Application_Programming_Interface>
|
||||
|
||||
ISP
|
||||
An Internet service provider is a business or organization that offers users
|
||||
access to the Internet and related services.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Internet_service_provider>
|
||||
|
||||
ISO
|
||||
International Standards Organization
|
||||
|
||||
.. seealso:: `ISO organization website <https://www.iso.org/about-us.html>`_
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/International_Organization_for_Standardization>
|
||||
|
||||
JPEG
|
||||
a most commonly used standard method of lossy compression for photographic images.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/JPEG>
|
||||
|
||||
JPG
|
||||
look at :term:`JPEG`
|
||||
|
||||
Key
|
||||
look at :term:`Index`
|
||||
|
||||
LATEX
|
||||
a document preparation system for the TeX typesetting program.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/LaTeX>
|
||||
|
||||
Mac
|
||||
Apple Macintosh is a line of personal computers designed, developed, manufactured, and marketed by Apple Inc.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Macintosh>
|
||||
|
||||
macOS
|
||||
the operating system which is included with all currently shipping Apple Macintosh computers in the consumer and professional markets.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/MacOS>
|
||||
|
||||
mbstring
|
||||
The PHP `mbstring` functions provide support for languages represented by multi-byte character sets, most notably UTF-8.
|
||||
|
||||
If you have troubles installing this extension, please follow :ref:`faqmysql`, it provides useful hints.
|
||||
|
||||
.. seealso:: <https://www.php.net/manual/en/book.mbstring.php>
|
||||
|
||||
Media type
|
||||
A media type (formerly known as MIME type) is a two-part identifier
|
||||
for file formats and format contents transmitted on the Internet.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Media_type>
|
||||
|
||||
MIME
|
||||
Multipurpose Internet Mail Extensions is
|
||||
an Internet Standard for the format of e-mail.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/MIME>
|
||||
|
||||
module
|
||||
modular extension for the Apache HTTP Server httpd.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Apache_HTTP_Server>
|
||||
|
||||
mod_proxy_fcgi
|
||||
an Apache module implementing a Fast CGI interface; PHP can be run as a CGI module, FastCGI, or
|
||||
directly as an Apache module.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Mod_proxy>
|
||||
|
||||
MySQL
|
||||
a multithreaded, multi-user, SQL (Structured Query Language) Database Management System (DBMS).
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/MySQL>
|
||||
|
||||
MySQLi
|
||||
the improved MySQL client PHP extension.
|
||||
|
||||
.. seealso:: `PHP manual for MySQL Improved Extension <https://www.php.net/manual/en/book.mysqli.php>`_
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/MySQLi>
|
||||
|
||||
mysql
|
||||
the MySQL client PHP extension.
|
||||
|
||||
.. seealso:: <https://www.php.net/manual/en/book.mysql.php>
|
||||
|
||||
OpenDocument
|
||||
an open standard for office documents.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/OpenDocument>
|
||||
|
||||
OS X
|
||||
look at :term:`macOS`.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/MacOS>
|
||||
|
||||
PDF
|
||||
Portable Document Format is a file format developed by Adobe Systems for
|
||||
representing two-dimensional documents in a device-independent and
|
||||
resolution-independent format.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/PDF>
|
||||
|
||||
PEAR
|
||||
the PHP Extension and Application Repository.
|
||||
|
||||
.. seealso:: `PEAR website <https://pear.php.net/>`_
|
||||
.. seealso:: `Wikipedia page for PEAR <https://en.wikipedia.org/wiki/PEAR>`_
|
||||
|
||||
PCRE
|
||||
Perl-Compatible Regular Expressions is the Perl-compatible regular
|
||||
expression functions for PHP
|
||||
|
||||
.. seealso:: <https://www.php.net/pcre>
|
||||
.. seealso:: `PHP manual for Perl-Compatible Regular Expressions <https://www.php.net/pcre>`_
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions>
|
||||
|
||||
PHP
|
||||
short for "PHP: Hypertext Preprocessor", is an open-source, reflective
|
||||
programming language used mainly for developing server-side applications
|
||||
and dynamic web content, and more recently, a broader range of software
|
||||
applications.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/PHP>
|
||||
|
||||
port
|
||||
a connection through which data is sent and received.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Port_(computer_networking)>
|
||||
|
||||
primary key
|
||||
A primary key is an index over one or more fields in a table with
|
||||
unique values for every single row in this table. Every table should have
|
||||
a primary key for easier accessing/identifying data in this table. There
|
||||
can only be one primary key per table and it is named always **PRIMARY**.
|
||||
In fact, a primary key is just an :term:`unique key` with the name
|
||||
**PRIMARY**. If no primary key is defined MySQL will use first *unique
|
||||
key* as primary key if there is one.
|
||||
|
||||
You can create the primary key when creating the table (in phpMyAdmin
|
||||
just check the primary key radio buttons for each field you wish to be
|
||||
part of the primary key).
|
||||
|
||||
You can also add a primary key to an existing table with `ALTER` `TABLE`
|
||||
or `CREATE` `INDEX` (in phpMyAdmin you can just click on 'add index' on
|
||||
the table structure page below the listed fields).
|
||||
|
||||
RFC
|
||||
Request for Comments (RFC) documents are a series of memoranda
|
||||
encompassing new research, innovations, and methodologies applicable to
|
||||
Internet technologies.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Request_for_Comments>
|
||||
|
||||
RFC 1952
|
||||
GZIP file format specification version 4.3
|
||||
|
||||
.. seealso:: :rfc:`1952`
|
||||
|
||||
Row (record, tuple)
|
||||
represents a single, implicitly structured data item in a table.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Row_(database)>
|
||||
|
||||
Server
|
||||
a computer system that provides services to other computing systems over a network.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Server_(computing)>
|
||||
|
||||
Storage Engines
|
||||
MySQL can use several different formats for storing data on disk, these
|
||||
are called storage engines or table types. phpMyAdmin allows a user to
|
||||
change their storage engine for a particular table through the operations
|
||||
tab.
|
||||
|
||||
Common table types are InnoDB and MyISAM, though many others exist and
|
||||
may be desirable in some situations.
|
||||
|
||||
.. seealso:: `MySQL doc chapter about Alternative Storage Engines <https://dev.mysql.com/doc/refman/8.0/en/storage-engines.html>`_
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Database_engine>
|
||||
|
||||
socket
|
||||
a form of inter-process communication.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Unix_domain_socket>
|
||||
|
||||
SSL
|
||||
Secure Sockets Layer, (now superseded by TLS) is a cryptographic protocol
|
||||
which provides secure communication on the Internet.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Transport_Layer_Security>
|
||||
|
||||
Stored procedure
|
||||
a subroutine available to applications accessing a relational database system
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Stored_procedure>
|
||||
|
||||
SQL
|
||||
Structured Query Language
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/SQL>
|
||||
|
||||
table
|
||||
a set of data elements (cells) that is organized, defined and stored as
|
||||
horizontal rows and vertical columns where each item can be uniquely
|
||||
identified by a label or key or by its position in relation to other
|
||||
items.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Table_(database)>
|
||||
|
||||
tar
|
||||
a type of archive file format, from "Tape Archive".
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Tar_(computing)>
|
||||
|
||||
TCP
|
||||
Transmission Control Protocol is one of the core protocols of the
|
||||
Internet protocol suite.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Internet_protocol_suite>
|
||||
|
||||
TCPDF
|
||||
PHP library to generate PDF files.
|
||||
|
||||
.. seealso:: <https://tcpdf.org/>
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/TCPDF>
|
||||
|
||||
trigger
|
||||
a procedural code that is automatically executed in response to certain events on a particular table or view in a database
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Database_trigger>
|
||||
|
||||
unique key
|
||||
A unique key is an index over one or more fields in a table which has a
|
||||
unique value for each row. The first unique key will be treated as
|
||||
:term:`primary key` if there is no *primary key* defined.
|
||||
|
||||
URL
|
||||
Uniform Resource Locator is a sequence of characters, conforming to a
|
||||
standardized format, that is used for referring to resources, such as
|
||||
documents and images on the Internet, by their location.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/URL>
|
||||
|
||||
Web server
|
||||
A computer (program) that is responsible for accepting HTTP requests from clients and serving them web pages.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Web_server>
|
||||
|
||||
XML
|
||||
Extensible Markup Language is a W3C-recommended general-purpose markup
|
||||
language for creating special-purpose markup languages, capable of
|
||||
describing many different kinds of data.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/XML>
|
||||
|
||||
ZIP
|
||||
a popular data compression and archival format.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Zip_(file_format)>
|
||||
|
||||
Zlib
|
||||
an open-source, cross-platform data compression library by `Jean-loup Gailly <https://en.wikipedia.org/wiki/Jean-Loup_Gailly>`_ and `Mark Adler <https://en.wikipedia.org/wiki/Mark_Adler>`_.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Zlib>
|
||||
|
||||
Content Security Policy
|
||||
The HTTP `Content-Security-Policy` response header allows web site administrators
|
||||
to control resources the user agent is allowed to load for a given page.
|
||||
|
||||
.. seealso:: <https://en.wikipedia.org/wiki/Content_Security_Policy>
|
||||
.. seealso:: <https://developer.mozilla.org/en/docs/Web/HTTP/CSP>
|
@ -0,0 +1,74 @@
|
||||
User management
|
||||
===============
|
||||
|
||||
User management is the process of controlling which users are allowed to
|
||||
connect to the MySQL server and what permissions they have on each database.
|
||||
phpMyAdmin does not handle user management, rather it passes the username and
|
||||
password on to MySQL, which then determines whether a user is permitted to
|
||||
perform a particular action. Within phpMyAdmin, administrators have full
|
||||
control over creating users, viewing and editing privileges for existing users,
|
||||
and removing users.
|
||||
|
||||
Within phpMyAdmin, user management is controlled via the :guilabel:`User accounts` tab
|
||||
from the main page. Users can be created, edited, and removed.
|
||||
|
||||
Creating a new user
|
||||
-------------------
|
||||
|
||||
To create a new user, click the :guilabel:`Add user account` link near the bottom
|
||||
of the :guilabel:`User accounts` page (you must be a "superuser", e.g., user "root").
|
||||
Use the textboxes and drop-downs to configure the user to your particular
|
||||
needs. You can then select whether to create a database for that user and grant
|
||||
specific global privileges. Once you've created the user (by clicking Go), you
|
||||
can define that user's permissions on a specific database (don't grant global
|
||||
privileges in that case). In general, users do not need any global privileges
|
||||
(other than USAGE), only permissions for their specific database.
|
||||
|
||||
Editing an existing user
|
||||
------------------------
|
||||
|
||||
To edit an existing user, simply click the pencil icon to the right of that
|
||||
user in the :guilabel:`User accounts` page. You can then edit their global- and
|
||||
database-specific privileges, change their password, or even copy those
|
||||
privileges to a new user.
|
||||
|
||||
Deleting a user
|
||||
---------------
|
||||
|
||||
From the :guilabel:`User accounts` page, check the checkbox for the user you wish to
|
||||
remove, select whether or not to also remove any databases of the same name (if
|
||||
they exist), and click Go.
|
||||
|
||||
Assigning privileges to user for a specific database
|
||||
----------------------------------------------------
|
||||
|
||||
Users are assigned to databases by editing the user record (from the
|
||||
:guilabel:`User accounts` link on the home page).
|
||||
If you are creating a user specifically for a given table
|
||||
you will have to create the user first (with no global privileges) and then go
|
||||
back and edit that user to add the table and privileges for the individual
|
||||
table.
|
||||
|
||||
.. _configurablemenus:
|
||||
|
||||
Configurable menus and user groups
|
||||
----------------------------------
|
||||
|
||||
By enabling :config:option:`$cfg['Servers'][$i]['users']` and
|
||||
:config:option:`$cfg['Servers'][$i]['usergroups']` you can customize what users
|
||||
will see in the phpMyAdmin navigation.
|
||||
|
||||
.. warning::
|
||||
|
||||
This feature only limits what a user sees, they are still able to use all the
|
||||
functions. So this can not be considered as a security limitation. Should
|
||||
you want to limit what users can do, use MySQL privileges to achieve that.
|
||||
|
||||
With this feature enabled, the :guilabel:`User accounts` management interface gains
|
||||
a second tab for managing :guilabel:`User groups`, where you can define what each
|
||||
group will view (see image below) and you can then assign each user to one of
|
||||
these groups. Users will be presented with a simplified user interface, which might be
|
||||
useful for inexperienced users who could be overwhelmed by all the features
|
||||
phpMyAdmin provides.
|
||||
|
||||
.. image:: images/usergroups.png
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,158 @@
|
||||
.. _transformations:
|
||||
|
||||
Transformations
|
||||
===============
|
||||
|
||||
.. note::
|
||||
|
||||
You need to have configured the :ref:`linked-tables` to use the transformations
|
||||
feature.
|
||||
|
||||
.. _transformationsintro:
|
||||
|
||||
Introduction
|
||||
++++++++++++
|
||||
|
||||
To enable transformations, you have to set up the ``column_info``
|
||||
table and the proper directives. Please see the :ref:`config` on how to do so.
|
||||
|
||||
phpMyAdmin has two different types of transformations: browser display
|
||||
transformations, which affect only how the data is shown when browsing
|
||||
through phpMyAdmin; and input transformations, which affect a value
|
||||
prior to being inserted through phpMyAdmin.
|
||||
You can apply different transformations to the contents of each
|
||||
column. Each transformation has options to define how it will affect the
|
||||
stored data.
|
||||
|
||||
Say you have a column ``filename`` which contains a filename. Normally
|
||||
you would see in phpMyAdmin only this filename. Using display transformations
|
||||
you can transform that filename into a HTML link, so you can click
|
||||
inside of the phpMyAdmin structure on the column's link and will see
|
||||
the file displayed in a new browser window. Using transformation
|
||||
options you can also specify strings to append/prepend to a string or
|
||||
the format you want the output stored in.
|
||||
|
||||
For a general overview of all available transformations and their
|
||||
options, you can either go to the ``Change`` link for an existing column
|
||||
or from the dialog to create a new column, in either case there is a link
|
||||
on that column structure page for "Browser display transformation" and
|
||||
"Input transformation" which will show more information about each
|
||||
transformation that is available on your system.
|
||||
|
||||
For a tutorial on how to effectively use transformations, see our
|
||||
`Link section <https://www.phpmyadmin.net/docs/>`_ on the
|
||||
official phpMyAdmin homepage.
|
||||
|
||||
.. _transformationshowto:
|
||||
|
||||
Usage
|
||||
+++++
|
||||
|
||||
Go to the table structure page (reached by clicking on
|
||||
the 'Structure' link for a table). There click on "Change" (or the change
|
||||
icon) and there you will see the five transformation--related fields at the end of the line.
|
||||
They are called ':term:`Media type`', 'Browser transformation' and
|
||||
'Transformation options'.
|
||||
|
||||
* The field ':term:`Media type`' is a drop-down field. Select the :term:`Media type` that
|
||||
corresponds to the column's contents. Please note that many transformations
|
||||
are inactive until a :term:`Media type` is selected.
|
||||
* The field 'Browser display transformation' is a drop-down field. You can
|
||||
choose from a hopefully growing amount of pre-defined transformations.
|
||||
See below for information on how to build your own transformation.
|
||||
There are global transformations and mimetype-bound transformations.
|
||||
Global transformations can be used for any mimetype. They will take
|
||||
the mimetype, if necessary, into regard. Mimetype-bound
|
||||
transformations usually only operate on a certain mimetype. There are
|
||||
transformations which operate on the main mimetype (like 'image'),
|
||||
which will most likely take the subtype into regard, and those who
|
||||
only operate on a specific subtype (like 'image/jpeg'). You can use
|
||||
transformations on mimetypes for which the function was not defined
|
||||
for. There is no security check for you selected the right
|
||||
transformation, so take care of what the output will be like.
|
||||
* The field 'Browser display transformation options' is a free-type textfield. You have
|
||||
to enter transform-function specific options here. Usually the
|
||||
transforms can operate with default options, but it is generally a
|
||||
good idea to look up the overview to see which options are necessary.
|
||||
Much like the ENUM/SET-Fields, you have to split up several options
|
||||
using the format 'a','b','c',...(NOTE THE MISSING BLANKS). This is
|
||||
because internally the options will be parsed as an array, leaving the
|
||||
first value the first element in the array, and so forth. If you want
|
||||
to specify a MIME character set you can define it in the
|
||||
transformation\_options. You have to put that outside of the pre-
|
||||
defined options of the specific mime-transform, as the last value of
|
||||
the set. Use the format "'; charset=XXX'". If you use a transform, for
|
||||
which you can specify 2 options and you want to append a character
|
||||
set, enter "'first parameter','second parameter','charset=us-ascii'".
|
||||
You can, however use the defaults for the parameters: "'','','charset
|
||||
=us-ascii'". The default options can be configured using
|
||||
:config:option:`$cfg['DefaultTransformations']`.
|
||||
* 'Input transformation' is another drop-down menu that corresponds exactly
|
||||
with the instructions above for "Browser display transformation" except
|
||||
these these affect the data before insertion in to the database. These are
|
||||
most commonly used to either provide a specialized editor (for example, using
|
||||
the phpMyAdmin SQL editor interface) or selector (such as for uploading an image).
|
||||
It's also possible to manipulate the data such as converting an IPv4 address to binary
|
||||
or parsing it through a regular expression.
|
||||
* Finally, 'Input transformation options' is the equivalent of the "Browser display
|
||||
transformation options" section above and is where optional and required parameters are entered.
|
||||
|
||||
.. _transformationsfiles:
|
||||
|
||||
File structure
|
||||
++++++++++++++
|
||||
|
||||
All specific transformations for mimetypes are defined through class
|
||||
files in the directory :file:`libraries/classes/Plugins/Transformations/`. Each of
|
||||
them extends a certain transformation abstract class declared in
|
||||
:file:`libraries/classes/Plugins/Transformations/Abs`.
|
||||
|
||||
They are stored in files to ease customization and to allow easy adding of
|
||||
new or custom transformations.
|
||||
|
||||
Because the user cannot enter their own mimetypes, it is kept certain that
|
||||
the transformations will always work. It makes no sense to apply a
|
||||
transformation to a mimetype the transform-function doesn't know to
|
||||
handle.
|
||||
|
||||
There is a file called :file:`libraries/classes/Plugins/Transformations.php` that provides some
|
||||
basic functions which can be included by any other transform function.
|
||||
|
||||
The file name convention is ``[Mimetype]_[Subtype]_[Transformation
|
||||
Name].php``, while the abstract class that it extends has the
|
||||
name ``[Transformation Name]TransformationsPlugin``. All of the
|
||||
methods that have to be implemented by a transformations plug-in are:
|
||||
|
||||
#. getMIMEType() and getMIMESubtype() in the main class;
|
||||
#. getName(), getInfo() and applyTransformation() in the abstract class
|
||||
it extends.
|
||||
|
||||
The getMIMEType(), getMIMESubtype() and getName() methods return the
|
||||
name of the MIME type, MIME Subtype and transformation accordingly.
|
||||
getInfo() returns the transformation's description and possible
|
||||
options it may receive and applyTransformation() is the method that
|
||||
does the actual work of the transformation plug-in.
|
||||
|
||||
Please see the :file:`libraries/classes/Plugins/Transformations/TEMPLATE` and
|
||||
:file:`libraries/classes/Plugins/Transformations/TEMPLATE\_ABSTRACT` files for adding
|
||||
your own transformation plug-in. You can also generate a new
|
||||
transformation plug-in (with or without the abstract transformation
|
||||
class), by using
|
||||
:file:`scripts/transformations_generator_plugin.sh` or
|
||||
:file:`scripts/transformations_generator_main_class.sh`.
|
||||
|
||||
The applyTransformation() method always gets passed three variables:
|
||||
|
||||
#. **$buffer** - Contains the text inside of the column. This is the
|
||||
text, you want to transform.
|
||||
#. **$options** - Contains any user-passed options to a transform
|
||||
function as an array.
|
||||
#. **$meta** - Contains an object with information about your column. The
|
||||
data is drawn from the output of the `mysql\_fetch\_field()
|
||||
<https://www.php.net/mysql_fetch_field>`_ function. This means, all
|
||||
object properties described on the `manual page
|
||||
<https://www.php.net/mysql_fetch_field>`_ are available in this
|
||||
variable and can be used to transform a column accordingly to
|
||||
unsigned/zerofill/not\_null/... properties. The $meta->mimetype
|
||||
variable contains the original :term:`Media type` of the column (i.e.
|
||||
'text/plain', 'image/jpeg' etc.)
|
@ -0,0 +1,69 @@
|
||||
.. _2fa:
|
||||
|
||||
Two-factor authentication
|
||||
=========================
|
||||
|
||||
.. versionadded:: 4.8.0
|
||||
|
||||
Since phpMyAdmin 4.8.0 you can configure two-factor authentication to be
|
||||
used when logging in. To use this, you first need to configure the
|
||||
:ref:`linked-tables`. Once this is done, every user can opt-in for the second
|
||||
authentication factor in the :guilabel:`Settings`.
|
||||
|
||||
When running phpMyAdmin from the Git source repository, the dependencies must be installed
|
||||
manually; the typical way of doing so is with the command:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
composer require pragmarx/google2fa-qrcode bacon/bacon-qr-code
|
||||
|
||||
Or when using a hardware security key with FIDO U2F:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
composer require code-lts/u2f-php-server
|
||||
|
||||
Authentication Application (2FA)
|
||||
--------------------------------
|
||||
|
||||
Using an application for authentication is a quite common approach based on HOTP and
|
||||
`TOTP <https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm>`_.
|
||||
It is based on transmitting a private key from phpMyAdmin to the authentication
|
||||
application and the application is then able to generate one time codes based
|
||||
on this key. The easiest way to enter the key in to the application from phpMyAdmin is
|
||||
through scanning a QR code.
|
||||
|
||||
There are dozens of applications available for mobile phones to implement these
|
||||
standards, the most widely used include:
|
||||
|
||||
* `FreeOTP for iOS, Android and Pebble <https://freeotp.github.io/>`_
|
||||
* `Authy for iOS, Android, Chrome, OS X <https://authy.com/>`_
|
||||
* `Google Authenticator for iOS <https://apps.apple.com/us/app/google-authenticator/id388497605>`_
|
||||
* `Google Authenticator for Android <https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2>`_
|
||||
* `LastPass Authenticator for iOS, Android, OS X, Windows <https://lastpass.com/auth/>`_
|
||||
|
||||
Hardware Security Key (FIDO U2F)
|
||||
--------------------------------
|
||||
|
||||
Using hardware tokens is considered to be more secure than a software based
|
||||
solution. phpMyAdmin supports `FIDO U2F <https://en.wikipedia.org/wiki/Universal_2nd_Factor>`_
|
||||
tokens.
|
||||
|
||||
There are several manufacturers of these tokens, for example:
|
||||
|
||||
* `youbico FIDO U2F Security Key <https://www.yubico.com/fido-u2f/>`_
|
||||
* `HyperFIDO <https://www.hypersecu.com/hyperfido>`_
|
||||
* `Trezor Hardware Wallet <https://trezor.io/?offer_id=12&aff_id=1592&source=phpmyadmin>`_ can act as an `U2F token <https://wiki.trezor.io/User_manual:Two-factor_Authentication_with_U2F>`_
|
||||
* `List of Two Factor Auth (2FA) Dongles <https://www.dongleauth.info/dongles/>`_
|
||||
|
||||
.. _simple2fa:
|
||||
|
||||
Simple two-factor authentication
|
||||
--------------------------------
|
||||
|
||||
This authentication is included for testing and demonstration purposes only as
|
||||
it really does not provide two-factor authentication, it just asks the user to confirm login by
|
||||
clicking on the button.
|
||||
|
||||
It should not be used in the production and is disabled unless
|
||||
:config:option:`$cfg['DBG']['simple2fa']` is set.
|
@ -0,0 +1,266 @@
|
||||
/*
|
||||
* classic.css_t
|
||||
* ~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- classic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
html {
|
||||
/* CSS hack for macOS's scrollbar (see #1125) */
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
background-color: #11303d;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
background-color: #1c4e63;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 230px;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 0 20px 30px 20px;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
padding: 9px 0 9px 0;
|
||||
text-align: center;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #ffffff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
background-color: #133f52;
|
||||
line-height: 30px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 {
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
color: #ffffff;
|
||||
font-size: 1.4em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
color: #ffffff;
|
||||
font-size: 1.3em;
|
||||
font-weight: normal;
|
||||
margin: 5px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.topless {
|
||||
margin: 5px 10px 10px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px;
|
||||
padding: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #98dbcc;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -- hyperlink styles ------------------------------------------------------ */
|
||||
|
||||
a {
|
||||
color: #355f7c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #355f7c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
background-color: #f2f2f2;
|
||||
font-weight: normal;
|
||||
color: #20435c;
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin: 20px -20px 10px -20px;
|
||||
padding: 3px 0 3px 10px;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; font-size: 200%; }
|
||||
div.body h2 { font-size: 160%; }
|
||||
div.body h3 { font-size: 140%; }
|
||||
div.body h4 { font-size: 120%; }
|
||||
div.body h5 { font-size: 110%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #c60f0f;
|
||||
font-size: 0.8em;
|
||||
padding: 0 4px 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
background-color: #c60f0f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li, div.body blockquote {
|
||||
text-align: justify;
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title + p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.admonition p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.admonition pre {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.admonition ul, div.admonition ol {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 5px;
|
||||
background-color: unset;
|
||||
color: unset;
|
||||
line-height: 120%;
|
||||
border: 1px solid #ac9;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #ecf0f3;
|
||||
padding: 0 1px 0 1px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
th, dl.field-list > dt {
|
||||
background-color: #ede;
|
||||
}
|
||||
|
||||
.warning code {
|
||||
background: #efc2c2;
|
||||
}
|
||||
|
||||
.note code {
|
||||
background: #d6d6d6;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
}
|
||||
|
||||
div.code-block-caption {
|
||||
color: #efefef;
|
||||
background-color: #1c4e63;
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* language_data.js
|
||||
* ~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This script contains the language-specific data used by searchtools.js,
|
||||
* namely the list of stopwords, stemmer, scorer and splitter.
|
||||
*
|
||||
* :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
|
||||
|
||||
|
||||
/* Non-minified version JS is _stemmer.js if file is provided */
|
||||
/**
|
||||
* Porter Stemmer
|
||||
*/
|
||||
var Stemmer = function() {
|
||||
|
||||
var step2list = {
|
||||
ational: 'ate',
|
||||
tional: 'tion',
|
||||
enci: 'ence',
|
||||
anci: 'ance',
|
||||
izer: 'ize',
|
||||
bli: 'ble',
|
||||
alli: 'al',
|
||||
entli: 'ent',
|
||||
eli: 'e',
|
||||
ousli: 'ous',
|
||||
ization: 'ize',
|
||||
ation: 'ate',
|
||||
ator: 'ate',
|
||||
alism: 'al',
|
||||
iveness: 'ive',
|
||||
fulness: 'ful',
|
||||
ousness: 'ous',
|
||||
aliti: 'al',
|
||||
iviti: 'ive',
|
||||
biliti: 'ble',
|
||||
logi: 'log'
|
||||
};
|
||||
|
||||
var step3list = {
|
||||
icate: 'ic',
|
||||
ative: '',
|
||||
alize: 'al',
|
||||
iciti: 'ic',
|
||||
ical: 'ic',
|
||||
ful: '',
|
||||
ness: ''
|
||||
};
|
||||
|
||||
var c = "[^aeiou]"; // consonant
|
||||
var v = "[aeiouy]"; // vowel
|
||||
var C = c + "[^aeiouy]*"; // consonant sequence
|
||||
var V = v + "[aeiou]*"; // vowel sequence
|
||||
|
||||
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
|
||||
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
|
||||
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
|
||||
var s_v = "^(" + C + ")?" + v; // vowel in stem
|
||||
|
||||
this.stemWord = function (w) {
|
||||
var stem;
|
||||
var suffix;
|
||||
var firstch;
|
||||
var origword = w;
|
||||
|
||||
if (w.length < 3)
|
||||
return w;
|
||||
|
||||
var re;
|
||||
var re2;
|
||||
var re3;
|
||||
var re4;
|
||||
|
||||
firstch = w.substr(0,1);
|
||||
if (firstch == "y")
|
||||
w = firstch.toUpperCase() + w.substr(1);
|
||||
|
||||
// Step 1a
|
||||
re = /^(.+?)(ss|i)es$/;
|
||||
re2 = /^(.+?)([^s])s$/;
|
||||
|
||||
if (re.test(w))
|
||||
w = w.replace(re,"$1$2");
|
||||
else if (re2.test(w))
|
||||
w = w.replace(re2,"$1$2");
|
||||
|
||||
// Step 1b
|
||||
re = /^(.+?)eed$/;
|
||||
re2 = /^(.+?)(ed|ing)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(fp[1])) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1];
|
||||
re2 = new RegExp(s_v);
|
||||
if (re2.test(stem)) {
|
||||
w = stem;
|
||||
re2 = /(at|bl|iz)$/;
|
||||
re3 = new RegExp("([^aeiouylsz])\\1$");
|
||||
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re2.test(w))
|
||||
w = w + "e";
|
||||
else if (re3.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
else if (re4.test(w))
|
||||
w = w + "e";
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1c
|
||||
re = /^(.+?)y$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(s_v);
|
||||
if (re.test(stem))
|
||||
w = stem + "i";
|
||||
}
|
||||
|
||||
// Step 2
|
||||
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step2list[suffix];
|
||||
}
|
||||
|
||||
// Step 3
|
||||
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step3list[suffix];
|
||||
}
|
||||
|
||||
// Step 4
|
||||
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
|
||||
re2 = /^(.+?)(s|t)(ion)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
if (re.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1] + fp[2];
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re2.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
|
||||
// Step 5
|
||||
re = /^(.+?)e$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
re2 = new RegExp(meq1);
|
||||
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
|
||||
w = stem;
|
||||
}
|
||||
re = /ll$/;
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re.test(w) && re2.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
|
||||
// and turn initial Y back to y
|
||||
if (firstch == "y")
|
||||
w = firstch.toLowerCase() + w.substr(1);
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var splitChars = (function() {
|
||||
var result = {};
|
||||
var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648,
|
||||
1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702,
|
||||
2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971,
|
||||
2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345,
|
||||
3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761,
|
||||
3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823,
|
||||
4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125,
|
||||
8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695,
|
||||
11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587,
|
||||
43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141];
|
||||
var i, j, start, end;
|
||||
for (i = 0; i < singles.length; i++) {
|
||||
result[singles[i]] = true;
|
||||
}
|
||||
var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709],
|
||||
[722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161],
|
||||
[1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568],
|
||||
[1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807],
|
||||
[1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047],
|
||||
[2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383],
|
||||
[2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450],
|
||||
[2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547],
|
||||
[2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673],
|
||||
[2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820],
|
||||
[2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946],
|
||||
[2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023],
|
||||
[3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173],
|
||||
[3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332],
|
||||
[3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481],
|
||||
[3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718],
|
||||
[3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791],
|
||||
[3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095],
|
||||
[4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205],
|
||||
[4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687],
|
||||
[4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968],
|
||||
[4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869],
|
||||
[5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102],
|
||||
[6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271],
|
||||
[6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592],
|
||||
[6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822],
|
||||
[6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167],
|
||||
[7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959],
|
||||
[7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143],
|
||||
[8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318],
|
||||
[8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483],
|
||||
[8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101],
|
||||
[10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567],
|
||||
[11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292],
|
||||
[12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444],
|
||||
[12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783],
|
||||
[12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311],
|
||||
[19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511],
|
||||
[42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774],
|
||||
[42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071],
|
||||
[43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263],
|
||||
[43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519],
|
||||
[43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647],
|
||||
[43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967],
|
||||
[44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295],
|
||||
[57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274],
|
||||
[64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007],
|
||||
[65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381],
|
||||
[65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]];
|
||||
for (i = 0; i < ranges.length; i++) {
|
||||
start = ranges[i][0];
|
||||
end = ranges[i][1];
|
||||
for (j = start; j <= end; j++) {
|
||||
result[j] = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
|
||||
function splitQuery(query) {
|
||||
var result = [];
|
||||
var start = -1;
|
||||
for (var i = 0; i < query.length; i++) {
|
||||
if (splitChars[query.charCodeAt(i)]) {
|
||||
if (start !== -1) {
|
||||
result.push(query.slice(start, i));
|
||||
start = -1;
|
||||
}
|
||||
} else if (start === -1) {
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
if (start !== -1) {
|
||||
result.push(query.slice(start));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,158 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Distributing and packaging phpMyAdmin — phpMyAdmin 5.2.0 documentation</title>
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/classic.css" type="text/css" />
|
||||
|
||||
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="copyright" title="Copyright" href="copyright.html" />
|
||||
<link rel="next" title="Copyright" href="copyright.html" />
|
||||
<link rel="prev" title="Security policy" href="security.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="copyright.html" title="Copyright"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="security.html" title="Security policy"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">phpMyAdmin 5.2.0 documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Distributing and packaging phpMyAdmin</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="distributing-and-packaging-phpmyadmin">
|
||||
<h1>Distributing and packaging phpMyAdmin<a class="headerlink" href="#distributing-and-packaging-phpmyadmin" title="Permalink to this headline">¶</a></h1>
|
||||
<p>This document is intended to give pieces of advice to people who want to
|
||||
redistribute phpMyAdmin inside other software packages such as Linux
|
||||
distribution or some all in one package including web server and MySQL
|
||||
server.</p>
|
||||
<p>Generally, you can customize some basic aspects (paths to some files and
|
||||
behavior) in <code class="file docutils literal notranslate"><span class="pre">libraries/vendor_config.php</span></code>.</p>
|
||||
<p>For example, if you want setup script to generate a config file in var, change
|
||||
<code class="docutils literal notranslate"><span class="pre">SETUP_CONFIG_FILE</span></code> to <code class="file docutils literal notranslate"><span class="pre">/var/lib/phpmyadmin/config.inc.php</span></code> and you
|
||||
will also probably want to skip directory writable check, so set
|
||||
<code class="docutils literal notranslate"><span class="pre">SETUP_DIR_WRITABLE</span></code> to false.</p>
|
||||
<div class="section" id="external-libraries">
|
||||
<h2>External libraries<a class="headerlink" href="#external-libraries" title="Permalink to this headline">¶</a></h2>
|
||||
<p>phpMyAdmin includes several external libraries, you might want to
|
||||
replace them with system ones if they are available, but please note
|
||||
that you should test whether the version you provide is compatible with the
|
||||
one we ship.</p>
|
||||
<p>Currently known list of external libraries:</p>
|
||||
<dl class="simple">
|
||||
<dt>js/vendor</dt><dd><p>jQuery js framework libraries and various js libraries.</p>
|
||||
</dd>
|
||||
<dt>vendor/</dt><dd><p>The download kit includes various Composer packages as
|
||||
dependencies.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="section" id="specific-files-licenses">
|
||||
<h2>Specific files LICENSES<a class="headerlink" href="#specific-files-licenses" title="Permalink to this headline">¶</a></h2>
|
||||
<p>phpMyAdmin distributed themes contain some content that is under licenses.</p>
|
||||
<ul class="simple">
|
||||
<li><p>The icons of the <cite>Original</cite> and <cite>pmahomme</cite> themes are from the <a class="reference external" href="http://www.famfamfam.com/lab/icons/silk/">Silk Icons</a>.</p></li>
|
||||
<li><p>Some icons of the <cite>Metro</cite> theme are from the <a class="reference external" href="http://www.famfamfam.com/lab/icons/silk/">Silk Icons</a>.</p></li>
|
||||
<li><p><cite>themes/*/img/b_rename.svg</cite> Is a <a class="reference external" href="https://thenounproject.com/Icons8/">Icons8</a>, icon from the <a class="reference external" href="https://thenounproject.com/Icons8/collection/android-l-icon-pack/">Android L Icon Pack Collection</a>. The icon <a class="reference external" href="https://thenounproject.com/term/rename/61456/">rename</a>.</p></li>
|
||||
<li><p><cite>themes/metro/img/user.svg</cite> Is a IcoMoon the <a class="reference external" href="https://github.com/Keyamoon/IcoMoon-Free/blob/master/SVG/114-user.svg">user</a></p></li>
|
||||
</ul>
|
||||
<p>CC BY 4.0 or GPL</p>
|
||||
</div>
|
||||
<div class="section" id="licenses-for-vendors">
|
||||
<h2>Licenses for vendors<a class="headerlink" href="#licenses-for-vendors" title="Permalink to this headline">¶</a></h2>
|
||||
<ul class="simple">
|
||||
<li><p>Silk Icons are under the <a class="reference external" href="http://www.famfamfam.com/lab/icons/silk/">CC BY 2.5 or CC BY 3.0</a> licenses.</p></li>
|
||||
<li><p><cite>rename</cite> from <cite>Icons8</cite> is under the <a class="reference external" href="https://creativecommons.org/publicdomain/zero/1.0/">“public domain”</a> (CC0 1.0) license.</p></li>
|
||||
<li><p>IcoMoon Free is under <a class="reference external" href="https://github.com/Keyamoon/IcoMoon-Free/blob/master/License.txt">“CC BY 4.0 or GPL”</a>.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Distributing and packaging phpMyAdmin</a><ul>
|
||||
<li><a class="reference internal" href="#external-libraries">External libraries</a></li>
|
||||
<li><a class="reference internal" href="#specific-files-licenses">Specific files LICENSES</a></li>
|
||||
<li><a class="reference internal" href="#licenses-for-vendors">Licenses for vendors</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>Previous topic</h4>
|
||||
<p class="topless"><a href="security.html"
|
||||
title="previous chapter">Security policy</a></p>
|
||||
<h4>Next topic</h4>
|
||||
<p class="topless"><a href="copyright.html"
|
||||
title="next chapter">Copyright</a></p>
|
||||
<div role="note" aria-label="source link">
|
||||
<h3>This Page</h3>
|
||||
<ul class="this-page-menu">
|
||||
<li><a href="_sources/vendors.rst.txt"
|
||||
rel="nofollow">Show Source</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" />
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>$('#searchbox').show(0);</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="right" >
|
||||
<a href="copyright.html" title="Copyright"
|
||||
>next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="security.html" title="Security policy"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">phpMyAdmin 5.2.0 documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Distributing and packaging phpMyAdmin</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer" role="contentinfo">
|
||||
© <a href="copyright.html">Copyright</a> 2012 - 2021, The phpMyAdmin devel team.
|
||||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 3.4.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,176 @@
|
||||
$(function () {
|
||||
Functions.checkNumberOfFields();
|
||||
});
|
||||
/**
|
||||
* Holds common parameters such as server, db, table, etc
|
||||
*
|
||||
* The content for this is normally loaded from Header.php or
|
||||
* Response.php and executed by ajax.js
|
||||
*
|
||||
* @test-module CommonParams
|
||||
*/
|
||||
|
||||
var CommonParams = function () {
|
||||
/**
|
||||
* @var {Object} params An associative array of key value pairs
|
||||
* @access private
|
||||
*/
|
||||
var params = {}; // The returned object is the public part of the module
|
||||
|
||||
return {
|
||||
/**
|
||||
* Saves all the key value pair that
|
||||
* are provided in the input array
|
||||
*
|
||||
* @param obj hash The input array
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
setAll: function (obj) {
|
||||
var updateNavigation = false;
|
||||
|
||||
for (var i in obj) {
|
||||
if (params[i] !== undefined && params[i] !== obj[i]) {
|
||||
if (i === 'db' || i === 'table') {
|
||||
updateNavigation = true;
|
||||
}
|
||||
}
|
||||
|
||||
params[i] = obj[i];
|
||||
}
|
||||
|
||||
if (updateNavigation && $('#pma_navigation_tree').hasClass('synced')) {
|
||||
Navigation.showCurrent();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves a value given its key
|
||||
* Returns empty string for undefined values
|
||||
*
|
||||
* @param {string} name The key
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
get: function (name) {
|
||||
return params[name];
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves a single key value pair
|
||||
*
|
||||
* @param {string} name The key
|
||||
* @param {string} value The value
|
||||
*
|
||||
* @return {CommonParams} For chainability
|
||||
*/
|
||||
set: function (name, value) {
|
||||
var updateNavigation = false;
|
||||
|
||||
if (name === 'db' || name === 'table' && params[name] !== value) {
|
||||
updateNavigation = true;
|
||||
}
|
||||
|
||||
params[name] = value;
|
||||
|
||||
if (updateNavigation && $('#pma_navigation_tree').hasClass('synced')) {
|
||||
Navigation.showCurrent();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the url query string using the saved parameters
|
||||
*
|
||||
* @param {string} separator New separator
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
getUrlQuery: function (separator) {
|
||||
var sep = typeof separator !== 'undefined' ? separator : '?';
|
||||
var common = this.get('common_query');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
|
||||
if (typeof common === 'string' && common.length > 0) {
|
||||
// If the last char is the separator, do not add it
|
||||
// Else add it
|
||||
common = common.substr(common.length - 1, common.length) === argsep ? common : common + argsep;
|
||||
}
|
||||
|
||||
return Functions.sprintf('%s%sserver=%s' + argsep + 'db=%s' + argsep + 'table=%s', sep, common, encodeURIComponent(this.get('server')), encodeURIComponent(this.get('db')), encodeURIComponent(this.get('table')));
|
||||
}
|
||||
};
|
||||
}();
|
||||
/**
|
||||
* Holds common parameters such as server, db, table, etc
|
||||
*
|
||||
* The content for this is normally loaded from Header.php or
|
||||
* Response.php and executed by ajax.js
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
|
||||
var CommonActions = {
|
||||
/**
|
||||
* Saves the database name when it's changed
|
||||
* and reloads the query window, if necessary
|
||||
*
|
||||
* @param {string} newDb new_db The name of the new database
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
setDb: function (newDb) {
|
||||
if (newDb !== CommonParams.get('db')) {
|
||||
CommonParams.setAll({
|
||||
'db': newDb,
|
||||
'table': ''
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens a database in the main part of the page
|
||||
*
|
||||
* @param {string} newDb The name of the new database
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
openDb: function (newDb) {
|
||||
CommonParams.set('db', newDb).set('table', '');
|
||||
this.refreshMain(CommonParams.get('opendb_url'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Refreshes the main frame
|
||||
*
|
||||
* @param {any} url Undefined to refresh to the same page
|
||||
* String to go to a different page, e.g: 'index.php'
|
||||
* @param {function | undefined} callback
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
refreshMain: function (url) {
|
||||
let callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
|
||||
var newUrl = url;
|
||||
|
||||
if (!newUrl) {
|
||||
newUrl = $('#selflink').find('a').attr('href') || window.location.pathname;
|
||||
newUrl = newUrl.substring(0, newUrl.indexOf('?'));
|
||||
}
|
||||
|
||||
if (newUrl.indexOf('?') !== -1) {
|
||||
newUrl += CommonParams.getUrlQuery(CommonParams.get('arg_separator'));
|
||||
} else {
|
||||
newUrl += CommonParams.getUrlQuery('?');
|
||||
}
|
||||
|
||||
$('<a></a>', {
|
||||
href: newUrl
|
||||
}).appendTo('body').trigger('click').remove();
|
||||
|
||||
if (typeof callback !== 'undefined') {
|
||||
AJAX.callback = callback;
|
||||
}
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Conditionally included if framing is not allowed
|
||||
*/
|
||||
if (self === top) {
|
||||
var styleElement = document.getElementById('cfs-style'); // check if styleElement has already been removed
|
||||
// to avoid frequently reported js error
|
||||
|
||||
if (typeof styleElement !== 'undefined' && styleElement !== null) {
|
||||
styleElement.parentNode.removeChild(styleElement);
|
||||
}
|
||||
} else {
|
||||
top.location = self.location;
|
||||
}
|
@ -0,0 +1,611 @@
|
||||
AJAX.registerTeardown('database/events.js', function () {
|
||||
$(document).off('click', 'a.ajax.add_anchor, a.ajax.edit_anchor');
|
||||
$(document).off('click', 'a.ajax.export_anchor');
|
||||
$(document).off('click', '#bulkActionExportButton');
|
||||
$(document).off('click', 'a.ajax.drop_anchor');
|
||||
$(document).off('click', '#bulkActionDropButton');
|
||||
$(document).off('change', 'select[name=item_type]');
|
||||
});
|
||||
const DatabaseEvents = {
|
||||
/**
|
||||
* @var $ajaxDialog Query object containing the reference to the
|
||||
* dialog that contains the editor
|
||||
*/
|
||||
$ajaxDialog: null,
|
||||
|
||||
/**
|
||||
* @var syntaxHiglighter Reference to the codemirror editor
|
||||
*/
|
||||
syntaxHiglighter: null,
|
||||
|
||||
/**
|
||||
* @var buttonOptions Object containing options for
|
||||
* the jQueryUI dialog buttons
|
||||
*/
|
||||
buttonOptions: {},
|
||||
|
||||
/**
|
||||
* Validate editor form fields.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
validate: function () {
|
||||
/**
|
||||
* @var $elm a jQuery object containing the reference
|
||||
* to an element that is being validated
|
||||
*/
|
||||
var $elm = null; // Common validation. At the very least the name
|
||||
// and the definition must be provided for an item
|
||||
|
||||
$elm = $('table.rte_table').last().find('input[name=item_name]');
|
||||
|
||||
if ($elm.val() === '') {
|
||||
$elm.trigger('focus');
|
||||
alert(Messages.strFormEmpty);
|
||||
return false;
|
||||
}
|
||||
|
||||
$elm = $('table.rte_table').find('textarea[name=item_definition]');
|
||||
|
||||
if ($elm.val() === '') {
|
||||
if (this.syntaxHiglighter !== null) {
|
||||
this.syntaxHiglighter.focus();
|
||||
} else {
|
||||
$('textarea[name=item_definition]').last().trigger('focus');
|
||||
}
|
||||
|
||||
alert(Messages.strFormEmpty);
|
||||
return false;
|
||||
} // The validation has so far passed, so now
|
||||
// we can validate item-specific fields.
|
||||
|
||||
|
||||
return this.validateCustom();
|
||||
},
|
||||
exportDialog: function ($this) {
|
||||
var $msg = Functions.ajaxShowMessage();
|
||||
|
||||
if ($this.attr('id') === 'bulkActionExportButton') {
|
||||
var combined = {
|
||||
success: true,
|
||||
title: Messages.strExport,
|
||||
message: '',
|
||||
error: ''
|
||||
}; // export anchors of all selected rows
|
||||
|
||||
var exportAnchors = $('input.checkall:checked').parents('tr').find('.export_anchor');
|
||||
var count = exportAnchors.length;
|
||||
var returnCount = 0;
|
||||
var p = $.when();
|
||||
exportAnchors.each(function () {
|
||||
var h = $(this).attr('href');
|
||||
p = p.then(function () {
|
||||
return $.get(h, {
|
||||
'ajax_request': true
|
||||
}, function (data) {
|
||||
returnCount++;
|
||||
|
||||
if (data.success === true) {
|
||||
combined.message += '\n' + data.message + '\n';
|
||||
|
||||
if (returnCount === count) {
|
||||
showExport(combined);
|
||||
}
|
||||
} else {
|
||||
// complain even if one export is failing
|
||||
combined.success = false;
|
||||
combined.error += '\n' + data.error + '\n';
|
||||
|
||||
if (returnCount === count) {
|
||||
showExport(combined);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$.get($this.attr('href'), {
|
||||
'ajax_request': true
|
||||
}, showExport);
|
||||
}
|
||||
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
|
||||
function showExport(data) {
|
||||
if (data.success === true) {
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
/**
|
||||
* @var button_options Object containing options
|
||||
* for jQueryUI dialog buttons
|
||||
*/
|
||||
|
||||
var buttonOptions = {};
|
||||
|
||||
buttonOptions[Messages.strClose] = function () {
|
||||
$(this).dialog('close').remove();
|
||||
};
|
||||
/**
|
||||
* Display the dialog to the user
|
||||
*/
|
||||
|
||||
|
||||
data.message = '<textarea cols="40" rows="15" class="w-100">' + data.message + '</textarea>';
|
||||
var $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
|
||||
width: 500,
|
||||
buttons: buttonOptions,
|
||||
title: data.title
|
||||
}); // Attach syntax highlighted editor to export dialog
|
||||
|
||||
/**
|
||||
* @var $elm jQuery object containing the reference
|
||||
* to the Export textarea.
|
||||
*/
|
||||
|
||||
var $elm = $ajaxDialog.find('textarea');
|
||||
Functions.getSqlEditor($elm);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
} // end showExport()
|
||||
|
||||
},
|
||||
// end exportDialog()
|
||||
editorDialog: function (isNew, $this) {
|
||||
var that = this;
|
||||
/**
|
||||
* @var $edit_row jQuery object containing the reference to
|
||||
* the row of the the item being edited
|
||||
* from the list of items
|
||||
*/
|
||||
|
||||
var $editRow = null;
|
||||
|
||||
if ($this.hasClass('edit_anchor')) {
|
||||
// Remember the row of the item being edited for later,
|
||||
// so that if the edit is successful, we can replace the
|
||||
// row with info about the modified item.
|
||||
$editRow = $this.parents('tr');
|
||||
}
|
||||
/**
|
||||
* @var $msg jQuery object containing the reference to
|
||||
* the AJAX message shown to the user
|
||||
*/
|
||||
|
||||
|
||||
var $msg = Functions.ajaxShowMessage();
|
||||
$.get($this.attr('href'), {
|
||||
'ajax_request': true
|
||||
}, function (data) {
|
||||
if (data.success === true) {
|
||||
// We have successfully fetched the editor form
|
||||
Functions.ajaxRemoveMessage($msg); // Now define the function that is called when
|
||||
// the user presses the "Go" button
|
||||
|
||||
that.buttonOptions[Messages.strGo] = function () {
|
||||
// Move the data from the codemirror editor back to the
|
||||
// textarea, where it can be used in the form submission.
|
||||
if (typeof CodeMirror !== 'undefined') {
|
||||
that.syntaxHiglighter.save();
|
||||
} // Validate editor and submit request, if passed.
|
||||
|
||||
|
||||
if (that.validate()) {
|
||||
/**
|
||||
* @var data Form data to be sent in the AJAX request
|
||||
*/
|
||||
var data = $('form.rte_form').last().serialize();
|
||||
$msg = Functions.ajaxShowMessage(Messages.strProcessingRequest);
|
||||
var url = $('form.rte_form').last().attr('action');
|
||||
$.post(url, data, function (data) {
|
||||
if (data.success === true) {
|
||||
// Item created successfully
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
Functions.slidingMessage(data.message);
|
||||
that.$ajaxDialog.dialog('close'); // If we are in 'edit' mode, we must
|
||||
// remove the reference to the old row.
|
||||
|
||||
if (mode === 'edit' && $editRow !== null) {
|
||||
$editRow.remove();
|
||||
} // Sometimes, like when moving a trigger from
|
||||
// a table to another one, the new row should
|
||||
// not be inserted into the list. In this case
|
||||
// "data.insert" will be set to false.
|
||||
|
||||
|
||||
if (data.insert) {
|
||||
// Insert the new row at the correct
|
||||
// location in the list of items
|
||||
|
||||
/**
|
||||
* @var text Contains the name of an item from
|
||||
* the list that is used in comparisons
|
||||
* to find the correct location where
|
||||
* to insert a new row.
|
||||
*/
|
||||
var text = '';
|
||||
/**
|
||||
* @var inserted Whether a new item has been
|
||||
* inserted in the list or not
|
||||
*/
|
||||
|
||||
var inserted = false;
|
||||
$('table.data').find('tr').each(function () {
|
||||
text = $(this).children('td').eq(0).find('strong').text().toUpperCase().trim();
|
||||
|
||||
if (text !== '' && text > data.name) {
|
||||
$(this).before(data.new_row);
|
||||
inserted = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!inserted) {
|
||||
// If we didn't manage to insert the row yet,
|
||||
// it must belong at the end of the list,
|
||||
// so we insert it there.
|
||||
$('table.data').append(data.new_row);
|
||||
} // Fade-in the new row
|
||||
|
||||
|
||||
$('tr.ajaxInsert').show('slow').removeClass('ajaxInsert');
|
||||
} else if ($('table.data').find('tr').has('td').length === 0) {
|
||||
// If we are not supposed to insert the new row,
|
||||
// we will now check if the table is empty and
|
||||
// needs to be hidden. This will be the case if
|
||||
// we were editing the only item in the list,
|
||||
// which we removed and will not be inserting
|
||||
// something else in its place.
|
||||
$('table.data').hide('slow', function () {
|
||||
$('#nothing2display').show('slow');
|
||||
});
|
||||
} // Now we have inserted the row at the correct
|
||||
// position, but surely at least some row classes
|
||||
// are wrong now. So we will iterate through
|
||||
// all rows and assign correct classes to them
|
||||
|
||||
/**
|
||||
* @var ct Count of processed rows
|
||||
*/
|
||||
|
||||
|
||||
var ct = 0;
|
||||
/**
|
||||
* @var rowclass Class to be attached to the row
|
||||
* that is being processed
|
||||
*/
|
||||
|
||||
var rowclass = '';
|
||||
$('table.data').find('tr').has('td').each(function () {
|
||||
rowclass = ct % 2 === 0 ? 'odd' : 'even';
|
||||
$(this).removeClass().addClass(rowclass);
|
||||
ct++;
|
||||
}); // If this is the first item being added, remove
|
||||
// the "No items" message and show the list.
|
||||
|
||||
if ($('table.data').find('tr').has('td').length > 0 && $('#nothing2display').is(':visible')) {
|
||||
$('#nothing2display').hide('slow', function () {
|
||||
$('table.data').show('slow');
|
||||
});
|
||||
}
|
||||
|
||||
Navigation.reload();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
} // end "if (that.validate())"
|
||||
|
||||
}; // end of function that handles the submission of the Editor
|
||||
|
||||
|
||||
that.buttonOptions[Messages.strClose] = function () {
|
||||
$(this).dialog('close');
|
||||
};
|
||||
/**
|
||||
* Display the dialog to the user
|
||||
*/
|
||||
|
||||
|
||||
that.$ajaxDialog = $('<div id="rteDialog">' + data.message + '</div>').dialog({
|
||||
width: 700,
|
||||
minWidth: 500,
|
||||
buttons: that.buttonOptions,
|
||||
// Issue #15810 - use button titles for modals (eg: new procedure)
|
||||
// Respect the order: title on href tag, href content, title sent in response
|
||||
title: $this.attr('title') || $this.text() || $(data.title).text(),
|
||||
modal: true,
|
||||
open: function () {
|
||||
$('#rteDialog').dialog('option', 'max-height', $(window).height());
|
||||
|
||||
if ($('#rteDialog').parents('.ui-dialog').height() > $(window).height()) {
|
||||
$('#rteDialog').dialog('option', 'height', $(window).height());
|
||||
}
|
||||
|
||||
$(this).find('input[name=item_name]').trigger('focus');
|
||||
$(this).find('input.datefield').each(function () {
|
||||
Functions.addDatepicker($(this).css('width', '95%'), 'date');
|
||||
});
|
||||
$(this).find('input.datetimefield').each(function () {
|
||||
Functions.addDatepicker($(this).css('width', '95%'), 'datetime');
|
||||
});
|
||||
$.datepicker.initialized = false;
|
||||
},
|
||||
close: function () {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
/**
|
||||
* @var mode Used to remember whether the editor is in
|
||||
* "Edit" or "Add" mode
|
||||
*/
|
||||
|
||||
var mode = 'add';
|
||||
|
||||
if ($('input[name=editor_process_edit]').length > 0) {
|
||||
mode = 'edit';
|
||||
} // Attach syntax highlighted editor to the definition
|
||||
|
||||
/**
|
||||
* @var elm jQuery object containing the reference to
|
||||
* the Definition textarea.
|
||||
*/
|
||||
|
||||
|
||||
var $elm = $('textarea[name=item_definition]').last();
|
||||
var linterOptions = {};
|
||||
linterOptions.eventEditor = true;
|
||||
that.syntaxHiglighter = Functions.getSqlEditor($elm, {}, 'both', linterOptions);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.get()
|
||||
},
|
||||
dropDialog: function ($this) {
|
||||
/**
|
||||
* @var $curr_row Object containing reference to the current row
|
||||
*/
|
||||
var $currRow = $this.parents('tr');
|
||||
/**
|
||||
* @var question String containing the question to be asked for confirmation
|
||||
*/
|
||||
|
||||
var question = $('<div></div>').text($currRow.children('td').children('.drop_sql').html()); // We ask for confirmation first here, before submitting the ajax request
|
||||
|
||||
$this.confirm(question, $this.attr('href'), function (url) {
|
||||
/**
|
||||
* @var msg jQuery object containing the reference to
|
||||
* the AJAX message shown to the user
|
||||
*/
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strProcessingRequest);
|
||||
var params = Functions.getJsConfirmCommonParam(this, $this.getPostData());
|
||||
$.post(url, params, function (data) {
|
||||
if (data.success === true) {
|
||||
/**
|
||||
* @var $table Object containing reference
|
||||
* to the main list of elements
|
||||
*/
|
||||
var $table = $currRow.parent(); // Check how many rows will be left after we remove
|
||||
// the one that the user has requested us to remove
|
||||
|
||||
if ($table.find('tr').length === 3) {
|
||||
// If there are two rows left, it means that they are
|
||||
// the header of the table and the rows that we are
|
||||
// about to remove, so after the removal there will be
|
||||
// nothing to show in the table, so we hide it.
|
||||
$table.hide('slow', function () {
|
||||
$(this).find('tr.even, tr.odd').remove();
|
||||
$('.withSelected').remove();
|
||||
$('#nothing2display').show('slow');
|
||||
});
|
||||
} else {
|
||||
$currRow.hide('slow', function () {
|
||||
$(this).remove(); // Now we have removed the row from the list, but maybe
|
||||
// some row classes are wrong now. So we will iterate
|
||||
// through all rows and assign correct classes to them.
|
||||
|
||||
/**
|
||||
* @var ct Count of processed rows
|
||||
*/
|
||||
|
||||
var ct = 0;
|
||||
/**
|
||||
* @var rowclass Class to be attached to the row
|
||||
* that is being processed
|
||||
*/
|
||||
|
||||
var rowclass = '';
|
||||
$table.find('tr').has('td').each(function () {
|
||||
rowclass = ct % 2 === 1 ? 'odd' : 'even';
|
||||
$(this).removeClass().addClass(rowclass);
|
||||
ct++;
|
||||
});
|
||||
});
|
||||
} // Get rid of the "Loading" message
|
||||
|
||||
|
||||
Functions.ajaxRemoveMessage($msg); // Show the query that we just executed
|
||||
|
||||
Functions.slidingMessage(data.sql_query);
|
||||
Navigation.reload();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
});
|
||||
},
|
||||
dropMultipleDialog: function ($this) {
|
||||
// We ask for confirmation here
|
||||
$this.confirm(Messages.strDropRTEitems, '', function () {
|
||||
/**
|
||||
* @var msg jQuery object containing the reference to
|
||||
* the AJAX message shown to the user
|
||||
*/
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strProcessingRequest); // drop anchors of all selected rows
|
||||
|
||||
var dropAnchors = $('input.checkall:checked').parents('tr').find('.drop_anchor');
|
||||
var success = true;
|
||||
var count = dropAnchors.length;
|
||||
var returnCount = 0;
|
||||
dropAnchors.each(function () {
|
||||
var $anchor = $(this);
|
||||
/**
|
||||
* @var $curr_row Object containing reference to the current row
|
||||
*/
|
||||
|
||||
var $currRow = $anchor.parents('tr');
|
||||
var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData());
|
||||
$.post($anchor.attr('href'), params, function (data) {
|
||||
returnCount++;
|
||||
|
||||
if (data.success === true) {
|
||||
/**
|
||||
* @var $table Object containing reference
|
||||
* to the main list of elements
|
||||
*/
|
||||
var $table = $currRow.parent(); // Check how many rows will be left after we remove
|
||||
// the one that the user has requested us to remove
|
||||
|
||||
if ($table.find('tr').length === 3) {
|
||||
// If there are two rows left, it means that they are
|
||||
// the header of the table and the rows that we are
|
||||
// about to remove, so after the removal there will be
|
||||
// nothing to show in the table, so we hide it.
|
||||
$table.hide('slow', function () {
|
||||
$(this).find('tr.even, tr.odd').remove();
|
||||
$('.withSelected').remove();
|
||||
$('#nothing2display').show('slow');
|
||||
});
|
||||
} else {
|
||||
$currRow.hide('fast', function () {
|
||||
// we will iterate
|
||||
// through all rows and assign correct classes to them.
|
||||
|
||||
/**
|
||||
* @var ct Count of processed rows
|
||||
*/
|
||||
var ct = 0;
|
||||
/**
|
||||
* @var rowclass Class to be attached to the row
|
||||
* that is being processed
|
||||
*/
|
||||
|
||||
var rowclass = '';
|
||||
$table.find('tr').has('td').each(function () {
|
||||
rowclass = ct % 2 === 1 ? 'odd' : 'even';
|
||||
$(this).removeClass().addClass(rowclass);
|
||||
ct++;
|
||||
});
|
||||
});
|
||||
$currRow.remove();
|
||||
}
|
||||
|
||||
if (returnCount === count) {
|
||||
if (success) {
|
||||
// Get rid of the "Loading" message
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
$('#rteListForm_checkall').prop({
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
});
|
||||
}
|
||||
|
||||
Navigation.reload();
|
||||
}
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
success = false;
|
||||
|
||||
if (returnCount === count) {
|
||||
Navigation.reload();
|
||||
}
|
||||
}
|
||||
}); // end $.post()
|
||||
}); // end drop_anchors.each()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate custom editor form fields.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
validateCustom: function () {
|
||||
/**
|
||||
* @var elm a jQuery object containing the reference
|
||||
* to an element that is being validated
|
||||
*/
|
||||
var $elm = null;
|
||||
|
||||
if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'RECURRING') {
|
||||
// The interval field must not be empty for recurring events
|
||||
$elm = this.$ajaxDialog.find('input[name=item_interval_value]');
|
||||
|
||||
if ($elm.val() === '') {
|
||||
$elm.trigger('focus');
|
||||
alert(Messages.strFormEmpty);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// The execute_at field must not be empty for "once off" events
|
||||
$elm = this.$ajaxDialog.find('input[name=item_execute_at]');
|
||||
|
||||
if ($elm.val() === '') {
|
||||
$elm.trigger('focus');
|
||||
alert(Messages.strFormEmpty);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
AJAX.registerOnload('database/events.js', function () {
|
||||
/**
|
||||
* Attach Ajax event handlers for the Add/Edit functionality.
|
||||
*/
|
||||
$(document).on('click', 'a.ajax.add_anchor, a.ajax.edit_anchor', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if ($(this).hasClass('add_anchor')) {
|
||||
$.datepicker.initialized = false;
|
||||
}
|
||||
|
||||
DatabaseEvents.editorDialog($(this).hasClass('add_anchor'), $(this));
|
||||
});
|
||||
/**
|
||||
* Attach Ajax event handlers for Export
|
||||
*/
|
||||
|
||||
$(document).on('click', 'a.ajax.export_anchor', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseEvents.exportDialog($(this));
|
||||
}); // end $(document).on()
|
||||
|
||||
$(document).on('click', '#bulkActionExportButton', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseEvents.exportDialog($(this));
|
||||
}); // end $(document).on()
|
||||
|
||||
/**
|
||||
* Attach Ajax event handlers for Drop functionality
|
||||
*/
|
||||
|
||||
$(document).on('click', 'a.ajax.drop_anchor', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseEvents.dropDialog($(this));
|
||||
}); // end $(document).on()
|
||||
|
||||
$(document).on('click', '#bulkActionDropButton', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseEvents.dropMultipleDialog($(this));
|
||||
}); // end $(document).on()
|
||||
|
||||
/**
|
||||
* Attach Ajax event handlers for the "Change event type" functionality, so that the correct
|
||||
* rows are shown in the editor when changing the event type
|
||||
*/
|
||||
|
||||
$(document).on('change', 'select[name=item_type]', function () {
|
||||
$(this).closest('table').find('tr.recurring_event_row, tr.onetime_event_row').toggle();
|
||||
});
|
||||
});
|
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @fileoverview function used in server privilege pages
|
||||
* @name Database Operations
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @requires js/functions.js
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ajax event handlers here for /database/operations
|
||||
*
|
||||
* Actions Ajaxified here:
|
||||
* Rename Database
|
||||
* Copy Database
|
||||
* Change Charset
|
||||
* Drop Database
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('database/operations.js', function () {
|
||||
$(document).off('submit', '#rename_db_form.ajax');
|
||||
$(document).off('submit', '#copy_db_form.ajax');
|
||||
$(document).off('submit', '#change_db_charset_form.ajax');
|
||||
$(document).off('click', '#drop_db_anchor.ajax');
|
||||
});
|
||||
AJAX.registerOnload('database/operations.js', function () {
|
||||
/**
|
||||
* Ajax event handlers for 'Rename Database'
|
||||
*/
|
||||
$(document).on('submit', '#rename_db_form.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (Functions.emptyCheckTheField(this, 'newname')) {
|
||||
Functions.ajaxShowMessage(Messages.strFormEmpty, false, 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldDbName = CommonParams.get('db');
|
||||
var newDbName = $('#new_db_name').val();
|
||||
|
||||
if (newDbName === oldDbName) {
|
||||
Functions.ajaxShowMessage(Messages.strDatabaseRenameToSameName, false, 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
var $form = $(this);
|
||||
var question = Functions.escapeHtml('CREATE DATABASE ' + newDbName + ' / DROP DATABASE ' + oldDbName);
|
||||
Functions.prepareForAjaxRequest($form);
|
||||
$form.confirm(question, $form.attr('action'), function (url) {
|
||||
Functions.ajaxShowMessage(Messages.strRenamingDatabases, false);
|
||||
$.post(url, $('#rename_db_form').serialize() + CommonParams.get('arg_separator') + 'is_js_confirmed=1', function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
Functions.ajaxShowMessage(data.message);
|
||||
CommonParams.set('db', data.newname);
|
||||
Navigation.reload(function () {
|
||||
$('#pma_navigation_tree').find('a:not(\'.expander\')').each(function () {
|
||||
var $thisAnchor = $(this);
|
||||
|
||||
if ($thisAnchor.text() === data.newname) {
|
||||
// simulate a click on the new db name
|
||||
// in navigation
|
||||
$thisAnchor.trigger('click');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
});
|
||||
}); // end Rename Database
|
||||
|
||||
/**
|
||||
* Ajax Event Handler for 'Copy Database'
|
||||
*/
|
||||
|
||||
$(document).on('submit', '#copy_db_form.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (Functions.emptyCheckTheField(this, 'newname')) {
|
||||
Functions.ajaxShowMessage(Messages.strFormEmpty, false, 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
Functions.ajaxShowMessage(Messages.strCopyingDatabase, false);
|
||||
var $form = $(this);
|
||||
Functions.prepareForAjaxRequest($form);
|
||||
$.post($form.attr('action'), $form.serialize(), function (data) {
|
||||
// use messages that stay on screen
|
||||
$('.alert-success, .alert-danger').fadeOut();
|
||||
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
if ($('#checkbox_switch').is(':checked')) {
|
||||
CommonParams.set('db', data.newname);
|
||||
CommonActions.refreshMain(false, function () {
|
||||
Functions.ajaxShowMessage(data.message);
|
||||
});
|
||||
} else {
|
||||
CommonParams.set('db', data.db);
|
||||
Functions.ajaxShowMessage(data.message);
|
||||
}
|
||||
|
||||
Navigation.reload();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
}); // end copy database
|
||||
|
||||
/**
|
||||
* Change tables columns visible only if change tables is checked
|
||||
*/
|
||||
|
||||
$('#span_change_all_tables_columns_collations').hide();
|
||||
$('#checkbox_change_all_tables_collations').on('click', function () {
|
||||
$('#span_change_all_tables_columns_collations').toggle();
|
||||
});
|
||||
/**
|
||||
* Ajax Event handler for 'Change Charset' of the database
|
||||
*/
|
||||
|
||||
$(document).on('submit', '#change_db_charset_form.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var $form = $(this);
|
||||
Functions.prepareForAjaxRequest($form);
|
||||
Functions.ajaxShowMessage(Messages.strChangingCharset);
|
||||
$.post($form.attr('action'), $form.serialize(), function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
Functions.ajaxShowMessage(data.message);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
}); // end change charset
|
||||
|
||||
/**
|
||||
* Ajax event handlers for Drop Database
|
||||
*/
|
||||
|
||||
$(document).on('click', '#drop_db_anchor.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var $link = $(this);
|
||||
/**
|
||||
* @var {String} question String containing the question to be asked for confirmation
|
||||
*/
|
||||
|
||||
var question = Messages.strDropDatabaseStrongWarning + ' ';
|
||||
question += Functions.sprintf(Messages.strDoYouReally, 'DROP DATABASE `' + Functions.escapeHtml(CommonParams.get('db') + '`'));
|
||||
var params = Functions.getJsConfirmCommonParam(this, $link.getPostData());
|
||||
$(this).confirm(question, $(this).attr('href'), function (url) {
|
||||
Functions.ajaxShowMessage(Messages.strProcessingRequest);
|
||||
$.post(url, params, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success) {
|
||||
// Database deleted successfully, refresh both the frames
|
||||
Navigation.reload();
|
||||
CommonParams.set('db', '');
|
||||
CommonActions.refreshMain('index.php?route=/server/databases', function () {
|
||||
Functions.ajaxShowMessage(data.message);
|
||||
});
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @fileoverview function used in QBE for DB
|
||||
* @name Database Operations
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @requires js/functions.js
|
||||
*
|
||||
*/
|
||||
|
||||
/* global sprintf */
|
||||
// js/vendor/sprintf.js
|
||||
function getFormatsText() {
|
||||
return {
|
||||
'=': ' = \'%s\'',
|
||||
'>': ' > \'%s\'',
|
||||
'>=': ' >= \'%s\'',
|
||||
'<': ' < \'%s\'',
|
||||
'<=': ' <= \'%s\'',
|
||||
'!=': ' != \'%s\'',
|
||||
'LIKE': ' LIKE \'%s\'',
|
||||
'LIKE %...%': ' LIKE \'%%%s%%\'',
|
||||
'NOT LIKE': ' NOT LIKE \'%s\'',
|
||||
'NOT LIKE %...%': ' NOT LIKE \'%%%s%%\'',
|
||||
'BETWEEN': ' BETWEEN \'%s\'',
|
||||
'NOT BETWEEN': ' NOT BETWEEN \'%s\'',
|
||||
'IS NULL': ' \'%s\' IS NULL',
|
||||
'IS NOT NULL': ' \'%s\' IS NOT NULL',
|
||||
'REGEXP': ' REGEXP \'%s\'',
|
||||
'REGEXP ^...$': ' REGEXP \'^%s$\'',
|
||||
'NOT REGEXP': ' NOT REGEXP \'%s\''
|
||||
};
|
||||
}
|
||||
|
||||
function generateCondition(criteriaDiv, table) {
|
||||
var query = '`' + Functions.escapeBacktick(table.val()) + '`.';
|
||||
query += '`' + Functions.escapeBacktick(table.siblings('.columnNameSelect').first().val()) + '`';
|
||||
|
||||
if (criteriaDiv.find('.criteria_rhs').first().val() === 'text') {
|
||||
var formatsText = getFormatsText();
|
||||
query += sprintf(formatsText[criteriaDiv.find('.criteria_op').first().val()], Functions.escapeSingleQuote(criteriaDiv.find('.rhs_text_val').first().val()));
|
||||
} else {
|
||||
query += ' ' + criteriaDiv.find('.criteria_op').first().val();
|
||||
query += ' `' + Functions.escapeBacktick(criteriaDiv.find('.tableNameSelect').first().val()) + '`.';
|
||||
query += '`' + Functions.escapeBacktick(criteriaDiv.find('.columnNameSelect').first().val()) + '`';
|
||||
}
|
||||
|
||||
return query;
|
||||
} // eslint-disable-next-line no-unused-vars
|
||||
|
||||
|
||||
function generateWhereBlock() {
|
||||
var count = 0;
|
||||
var query = '';
|
||||
$('.tableNameSelect').each(function () {
|
||||
var criteriaDiv = $(this).siblings('.jsCriteriaOptions').first();
|
||||
var useCriteria = $(this).siblings('.criteria_col').first();
|
||||
|
||||
if ($(this).val() !== '' && useCriteria.prop('checked')) {
|
||||
if (count > 0) {
|
||||
criteriaDiv.find('input.logical_op').each(function () {
|
||||
if ($(this).prop('checked')) {
|
||||
query += ' ' + $(this).val() + ' ';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
query += generateCondition(criteriaDiv, $(this));
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return query;
|
||||
}
|
||||
|
||||
function generateJoin(newTable, tableAliases, fk) {
|
||||
var query = '';
|
||||
query += ' \n\tLEFT JOIN ' + '`' + Functions.escapeBacktick(newTable) + '`';
|
||||
|
||||
if (tableAliases[fk.TABLE_NAME][0] !== '') {
|
||||
query += ' AS `' + Functions.escapeBacktick(tableAliases[newTable][0]) + '`';
|
||||
query += ' ON `' + Functions.escapeBacktick(tableAliases[fk.TABLE_NAME][0]) + '`';
|
||||
} else {
|
||||
query += ' ON `' + Functions.escapeBacktick(fk.TABLE_NAME) + '`';
|
||||
}
|
||||
|
||||
query += '.`' + fk.COLUMN_NAME + '`';
|
||||
|
||||
if (tableAliases[fk.REFERENCED_TABLE_NAME][0] !== '') {
|
||||
query += ' = `' + Functions.escapeBacktick(tableAliases[fk.REFERENCED_TABLE_NAME][0]) + '`';
|
||||
} else {
|
||||
query += ' = `' + Functions.escapeBacktick(fk.REFERENCED_TABLE_NAME) + '`';
|
||||
}
|
||||
|
||||
query += '.`' + fk.REFERENCED_COLUMN_NAME + '`';
|
||||
return query;
|
||||
}
|
||||
|
||||
function existReference(table, fk, usedTables) {
|
||||
var isReferredBy = fk.TABLE_NAME === table && usedTables.includes(fk.REFERENCED_TABLE_NAME);
|
||||
var isReferencedBy = fk.REFERENCED_TABLE_NAME === table && usedTables.includes(fk.TABLE_NAME);
|
||||
return isReferredBy || isReferencedBy;
|
||||
}
|
||||
|
||||
function tryJoinTable(table, tableAliases, usedTables, foreignKeys) {
|
||||
for (var i = 0; i < foreignKeys.length; i++) {
|
||||
var fk = foreignKeys[i];
|
||||
|
||||
if (existReference(table, fk, usedTables)) {
|
||||
return generateJoin(table, tableAliases, fk);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function appendTable(table, tableAliases, usedTables, foreignKeys) {
|
||||
var query = tryJoinTable(table, tableAliases, usedTables, foreignKeys);
|
||||
|
||||
if (query === '') {
|
||||
if (usedTables.length > 0) {
|
||||
query += '\n\t, ';
|
||||
}
|
||||
|
||||
query += '`' + Functions.escapeBacktick(table) + '`';
|
||||
|
||||
if (tableAliases[table][0] !== '') {
|
||||
query += ' AS `' + Functions.escapeBacktick(tableAliases[table][0]) + '`';
|
||||
}
|
||||
}
|
||||
|
||||
usedTables.push(table);
|
||||
return query;
|
||||
} // eslint-disable-next-line no-unused-vars
|
||||
|
||||
|
||||
function generateFromBlock(tableAliases, foreignKeys) {
|
||||
var usedTables = [];
|
||||
var query = '';
|
||||
|
||||
for (var table in tableAliases) {
|
||||
if (tableAliases.hasOwnProperty(table)) {
|
||||
query += appendTable(table, tableAliases, usedTables, foreignKeys);
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
/**
|
||||
* JavaScript functions used on Database Search page
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires js/functions.js
|
||||
*
|
||||
* @package PhpMyAdmin
|
||||
*/
|
||||
|
||||
/* global makeGrid */
|
||||
// js/makegrid.js
|
||||
|
||||
/**
|
||||
* AJAX script for the Database Search page.
|
||||
*
|
||||
* Actions ajaxified here:
|
||||
* Retrieve result of SQL query
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('database/search.js', function () {
|
||||
$('a.browse_results').off('click');
|
||||
$('a.delete_results').off('click');
|
||||
$('#buttonGo').off('click');
|
||||
$('#togglesearchresultlink').off('click');
|
||||
$('#togglequerybox').off('click');
|
||||
$('#togglesearchformlink').off('click');
|
||||
$('#select_all').off('click');
|
||||
$('#unselect_all').off('click');
|
||||
$(document).off('submit', '#db_search_form.ajax');
|
||||
});
|
||||
AJAX.registerOnload('database/search.js', function () {
|
||||
/** Hide the table link in the initial search result */
|
||||
var icon = Functions.getImage('s_tbl', '', {
|
||||
'id': 'table-image'
|
||||
}).toString();
|
||||
$('#table-info').prepend(icon).hide();
|
||||
/** Hide the browse and deleted results in the new search criteria */
|
||||
|
||||
$('#buttonGo').on('click', function () {
|
||||
$('#table-info').hide();
|
||||
$('#browse-results').hide();
|
||||
$('#sqlqueryform').hide();
|
||||
$('#togglequerybox').hide();
|
||||
});
|
||||
/**
|
||||
* Prepare a div containing a link for toggle the search results
|
||||
*/
|
||||
|
||||
$('#togglesearchresultsdiv')
|
||||
/** don't show it until we have results on-screen */
|
||||
.hide();
|
||||
/**
|
||||
* Changing the displayed text according to
|
||||
* the hide/show criteria in search result forms
|
||||
*/
|
||||
|
||||
$('#togglesearchresultlink').html(Messages.strHideSearchResults).on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#searchresults').slideToggle();
|
||||
|
||||
if ($link.text() === Messages.strHideSearchResults) {
|
||||
$link.text(Messages.strShowSearchResults);
|
||||
} else {
|
||||
$link.text(Messages.strHideSearchResults);
|
||||
}
|
||||
/** avoid default click action */
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
/**
|
||||
* Prepare a div containing a link for toggle the search form,
|
||||
* otherwise it's incorrectly displayed after a couple of clicks
|
||||
*/
|
||||
|
||||
$('#togglesearchformdiv').hide(); // don't show it until we have results on-screen
|
||||
|
||||
/**
|
||||
* Changing the displayed text according to
|
||||
* the hide/show criteria in search form
|
||||
*/
|
||||
|
||||
$('#togglequerybox').hide().on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#sqlqueryform').slideToggle('medium');
|
||||
|
||||
if ($link.text() === Messages.strHideQueryBox) {
|
||||
$link.text(Messages.strShowQueryBox);
|
||||
} else {
|
||||
$link.text(Messages.strHideQueryBox);
|
||||
}
|
||||
/** avoid default click action */
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
/** don't show it until we have results on-screen */
|
||||
|
||||
/**
|
||||
* Changing the displayed text according to
|
||||
* the hide/show criteria in search criteria form
|
||||
*/
|
||||
|
||||
$('#togglesearchformlink').html(Messages.strShowSearchCriteria).on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#db_search_form').slideToggle();
|
||||
|
||||
if ($link.text() === Messages.strHideSearchCriteria) {
|
||||
$link.text(Messages.strShowSearchCriteria);
|
||||
} else {
|
||||
$link.text(Messages.strHideSearchCriteria);
|
||||
}
|
||||
/** avoid default click action */
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
/*
|
||||
* Ajax Event handler for retrieving the results from a table
|
||||
*/
|
||||
|
||||
$(document).on('click', 'a.browse_results', function (e) {
|
||||
e.preventDefault();
|
||||
/** Hides the results shown by the delete criteria */
|
||||
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strBrowsing, false);
|
||||
$('#sqlqueryform').hide();
|
||||
$('#togglequerybox').hide();
|
||||
/** Load the browse results to the page */
|
||||
|
||||
$('#table-info').show();
|
||||
var tableName = $(this).data('table-name');
|
||||
$('#table-link').attr({
|
||||
'href': $(this).attr('href')
|
||||
}).text(tableName);
|
||||
var url = $(this).attr('href') + '#searchresults';
|
||||
var browseSql = $(this).data('browse-sql');
|
||||
var params = {
|
||||
'ajax_request': true,
|
||||
'is_js_confirmed': true,
|
||||
'sql_query': browseSql
|
||||
};
|
||||
$.post(url, params, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success) {
|
||||
$('#browse-results').html(data.message);
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
$('.table_results').each(function () {
|
||||
makeGrid(this, true, true, true, true);
|
||||
});
|
||||
$('#browse-results').show();
|
||||
Functions.highlightSql($('#browse-results'));
|
||||
$('html, body').animate({
|
||||
scrollTop: $('#browse-results').offset().top
|
||||
}, 1000);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
/*
|
||||
* Ajax Event handler for deleting the results from a table
|
||||
*/
|
||||
|
||||
$(document).on('click', 'a.delete_results', function (e) {
|
||||
e.preventDefault();
|
||||
/** Hides the results shown by the browse criteria */
|
||||
|
||||
$('#table-info').hide();
|
||||
$('#sqlqueryform').hide();
|
||||
$('#togglequerybox').hide();
|
||||
/** Conformation message for deletion */
|
||||
|
||||
var msg = Functions.sprintf(Messages.strConfirmDeleteResults, $(this).data('table-name'));
|
||||
|
||||
if (confirm(msg)) {
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strDeleting, false);
|
||||
/** Load the deleted option to the page*/
|
||||
|
||||
$('#sqlqueryform').html('');
|
||||
var params = {
|
||||
'ajax_request': true,
|
||||
'is_js_confirmed': true,
|
||||
'sql_query': $(this).data('delete-sql')
|
||||
};
|
||||
var url = $(this).attr('href');
|
||||
$.post(url, params, function (data) {
|
||||
if (typeof data === 'undefined' || !data.success) {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
$('#sqlqueryform').html(data.sql_query);
|
||||
/** Refresh the search results after the deletion */
|
||||
|
||||
$('#buttonGo').trigger('click');
|
||||
$('#togglequerybox').html(Messages.strHideQueryBox);
|
||||
/** Show the results of the deletion option */
|
||||
|
||||
$('#browse-results').hide();
|
||||
$('#sqlqueryform').show();
|
||||
$('#togglequerybox').show();
|
||||
$('html, body').animate({
|
||||
scrollTop: $('#browse-results').offset().top
|
||||
}, 1000);
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
});
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Ajax Event handler for retrieving the result of an SQL Query
|
||||
*/
|
||||
|
||||
$(document).on('submit', '#db_search_form.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if ($('#criteriaTables :selected').length === 0) {
|
||||
Functions.ajaxShowMessage(Messages.strNoTableSelected);
|
||||
return;
|
||||
}
|
||||
|
||||
var $msgbox = Functions.ajaxShowMessage(Messages.strSearching, false); // jQuery object to reuse
|
||||
|
||||
var $form = $(this);
|
||||
Functions.prepareForAjaxRequest($form);
|
||||
var url = $form.serialize() + CommonParams.get('arg_separator') + 'submit_search=' + $('#buttonGo').val();
|
||||
$.post($form.attr('action'), url, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
// found results
|
||||
$('#searchresults').html(data.message);
|
||||
$('#togglesearchresultlink') // always start with the Show message
|
||||
.text(Messages.strHideSearchResults);
|
||||
$('#togglesearchresultsdiv') // now it's time to show the div containing the link
|
||||
.show();
|
||||
$('#searchresults').show();
|
||||
$('#db_search_form') // workaround for Chrome problem (bug #3168569)
|
||||
.slideToggle().hide();
|
||||
$('#togglesearchformlink') // always start with the Show message
|
||||
.text(Messages.strShowSearchCriteria);
|
||||
$('#togglesearchformdiv') // now it's time to show the div containing the link
|
||||
.show();
|
||||
} else {
|
||||
// error message (zero rows)
|
||||
$('#searchresults').html(data.error).show();
|
||||
}
|
||||
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
});
|
||||
});
|
||||
$('#select_all').on('click', function () {
|
||||
Functions.setSelectOptions('db_search', 'criteriaTables[]', true);
|
||||
return false;
|
||||
});
|
||||
$('#unselect_all').on('click', function () {
|
||||
Functions.setSelectOptions('db_search', 'criteriaTables[]', false);
|
||||
return false;
|
||||
});
|
||||
}); // end $()
|
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Unbind all event handlers before tearing down the page
|
||||
*/
|
||||
AJAX.registerTeardown('database/tracking.js', function () {
|
||||
$('body').off('click', '#trackedForm.ajax button[name="submit_mult"], #trackedForm.ajax input[name="submit_mult"]');
|
||||
$('body').off('click', '#untrackedForm.ajax button[name="submit_mult"], #untrackedForm.ajax input[name="submit_mult"]');
|
||||
$('body').off('click', 'a.delete_tracking_anchor.ajax');
|
||||
});
|
||||
/**
|
||||
* Bind event handlers
|
||||
*/
|
||||
|
||||
AJAX.registerOnload('database/tracking.js', function () {
|
||||
var $versions = $('#versions');
|
||||
$versions.find('tr').first().find('th').append($('<div class="sorticon"></div>'));
|
||||
$versions.tablesorter({
|
||||
sortList: [[1, 0]],
|
||||
headers: {
|
||||
0: {
|
||||
sorter: false
|
||||
},
|
||||
2: {
|
||||
sorter: 'integer'
|
||||
},
|
||||
5: {
|
||||
sorter: false
|
||||
},
|
||||
6: {
|
||||
sorter: false
|
||||
},
|
||||
7: {
|
||||
sorter: false
|
||||
}
|
||||
}
|
||||
});
|
||||
var $noVersions = $('#noversions');
|
||||
$noVersions.find('tr').first().find('th').append($('<div class="sorticon"></div>'));
|
||||
$noVersions.tablesorter({
|
||||
sortList: [[1, 0]],
|
||||
headers: {
|
||||
0: {
|
||||
sorter: false
|
||||
},
|
||||
2: {
|
||||
sorter: false
|
||||
}
|
||||
}
|
||||
});
|
||||
var $body = $('body');
|
||||
/**
|
||||
* Handles multi submit for tracked tables
|
||||
*/
|
||||
|
||||
$body.on('click', '#trackedForm.ajax button[name="submit_mult"], #trackedForm.ajax input[name="submit_mult"]', function (e) {
|
||||
e.preventDefault();
|
||||
var $button = $(this);
|
||||
var $form = $button.parent('form');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val();
|
||||
|
||||
if ($button.val() === 'delete_tracking') {
|
||||
var question = Messages.strDeleteTrackingDataMultiple;
|
||||
$button.confirm(question, $form.attr('action'), function (url) {
|
||||
Functions.ajaxShowMessage(Messages.strDeletingTrackingData);
|
||||
AJAX.source = $form;
|
||||
$.post(url, submitData, AJAX.responseHandler);
|
||||
});
|
||||
} else {
|
||||
Functions.ajaxShowMessage();
|
||||
AJAX.source = $form;
|
||||
$.post($form.attr('action'), submitData, AJAX.responseHandler);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Handles multi submit for untracked tables
|
||||
*/
|
||||
|
||||
$body.on('click', '#untrackedForm.ajax button[name="submit_mult"], #untrackedForm.ajax input[name="submit_mult"]', function (e) {
|
||||
e.preventDefault();
|
||||
var $button = $(this);
|
||||
var $form = $button.parent('form');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val();
|
||||
Functions.ajaxShowMessage();
|
||||
AJAX.source = $form;
|
||||
$.post($form.attr('action'), submitData, AJAX.responseHandler);
|
||||
});
|
||||
/**
|
||||
* Ajax Event handler for 'Delete tracking'
|
||||
*/
|
||||
|
||||
$body.on('click', 'a.delete_tracking_anchor.ajax', function (e) {
|
||||
e.preventDefault();
|
||||
var $anchor = $(this);
|
||||
var question = Messages.strDeleteTrackingData;
|
||||
$anchor.confirm(question, $anchor.attr('href'), function (url) {
|
||||
Functions.ajaxShowMessage(Messages.strDeletingTrackingData);
|
||||
AJAX.source = $anchor;
|
||||
var argSep = CommonParams.get('arg_separator');
|
||||
var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData());
|
||||
params += argSep + 'ajax_page_request=1';
|
||||
$.post(url, params, AJAX.responseHandler);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Initializes the data required to run Designer, then fires it up.
|
||||
*/
|
||||
|
||||
/* global DesignerOfflineDB */
|
||||
// js/designer/database.js
|
||||
|
||||
/* global DesignerHistory */
|
||||
// js/designer/history.js
|
||||
|
||||
/* global DesignerMove */
|
||||
// js/designer/move.js
|
||||
|
||||
/* global DesignerPage */
|
||||
// js/designer/page.js
|
||||
|
||||
/* global designerConfig */
|
||||
// templates/database/designer/main.twig
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
var jTabs;
|
||||
var hTabs;
|
||||
var contr;
|
||||
var displayField;
|
||||
var server;
|
||||
var selectedPage;
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
var db;
|
||||
var designerTablesEnabled;
|
||||
AJAX.registerTeardown('designer/init.js', function () {
|
||||
$('.trigger').off('click');
|
||||
});
|
||||
AJAX.registerOnload('designer/init.js', function () {
|
||||
$('.trigger').on('click', function () {
|
||||
$('.panel').toggle('fast');
|
||||
$(this).toggleClass('active');
|
||||
$('#ab').accordion('refresh');
|
||||
return false;
|
||||
});
|
||||
jTabs = designerConfig.scriptTables.j_tabs;
|
||||
hTabs = designerConfig.scriptTables.h_tabs;
|
||||
contr = designerConfig.scriptContr;
|
||||
displayField = designerConfig.scriptDisplayField;
|
||||
server = designerConfig.server;
|
||||
selectedPage = designerConfig.displayPage;
|
||||
db = designerConfig.db;
|
||||
designerTablesEnabled = designerConfig.tablesEnabled;
|
||||
DesignerMove.main();
|
||||
|
||||
if (!designerTablesEnabled) {
|
||||
DesignerOfflineDB.open(function (success) {
|
||||
if (success) {
|
||||
DesignerPage.showTablesInLandingPage(db);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#query_Aggregate_Button').on('click', function () {
|
||||
$('#query_Aggregate').css('display', 'none');
|
||||
});
|
||||
$('#query_having_button').on('click', function () {
|
||||
$('#query_having').css('display', 'none');
|
||||
});
|
||||
$('#query_rename_to_button').on('click', function () {
|
||||
$('#query_rename_to').css('display', 'none');
|
||||
});
|
||||
$('#build_query_button').on('click', function () {
|
||||
DesignerHistory.buildQuery('SQL Query on Database', 0);
|
||||
});
|
||||
$('#query_where_button').on('click', function () {
|
||||
$('#query_where').css('display', 'none');
|
||||
});
|
||||
});
|
@ -0,0 +1,208 @@
|
||||
/* global DesignerOfflineDB */
|
||||
// js/designer/database.js
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
/* global db, selectedPage:writable */
|
||||
// js/designer/init.js
|
||||
|
||||
/* global DesignerMove */
|
||||
// js/designer/move.js
|
||||
|
||||
/* global DesignerObjects */
|
||||
// js/designer/objects.js
|
||||
var DesignerPage = {};
|
||||
|
||||
DesignerPage.showTablesInLandingPage = function (db) {
|
||||
DesignerPage.loadFirstPage(db, function (page) {
|
||||
if (page) {
|
||||
DesignerPage.loadHtmlForPage(page.pgNr);
|
||||
selectedPage = page.pgNr;
|
||||
} else {
|
||||
DesignerPage.showNewPageTables(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.saveToNewPage = function (db, pageName, tablePositions, callback) {
|
||||
DesignerPage.createNewPage(db, pageName, function (page) {
|
||||
if (page) {
|
||||
var tblCords = [];
|
||||
|
||||
var saveCallback = function (id) {
|
||||
tblCords.push(id);
|
||||
|
||||
if (tablePositions.length === tblCords.length) {
|
||||
page.tblCords = tblCords;
|
||||
DesignerOfflineDB.addObject('pdf_pages', page);
|
||||
}
|
||||
};
|
||||
|
||||
for (var pos = 0; pos < tablePositions.length; pos++) {
|
||||
tablePositions[pos].pdfPgNr = page.pgNr;
|
||||
DesignerPage.saveTablePositions(tablePositions[pos], saveCallback);
|
||||
}
|
||||
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(page);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.saveToSelectedPage = function (db, pageId, pageName, tablePositions, callback) {
|
||||
DesignerPage.deletePage(pageId);
|
||||
DesignerPage.saveToNewPage(db, pageName, tablePositions, function (page) {
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(page);
|
||||
}
|
||||
|
||||
selectedPage = page.pgNr;
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.createNewPage = function (db, pageName, callback) {
|
||||
var newPage = new DesignerObjects.PdfPage(db, pageName);
|
||||
DesignerOfflineDB.addObject('pdf_pages', newPage, function (pgNr) {
|
||||
newPage.pgNr = pgNr;
|
||||
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(newPage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.saveTablePositions = function (positions, callback) {
|
||||
DesignerOfflineDB.addObject('table_coords', positions, callback);
|
||||
};
|
||||
|
||||
DesignerPage.createPageList = function (db, callback) {
|
||||
DesignerOfflineDB.loadAllObjects('pdf_pages', function (pages) {
|
||||
var html = '';
|
||||
|
||||
for (var p = 0; p < pages.length; p++) {
|
||||
var page = pages[p];
|
||||
|
||||
if (page.dbName === db) {
|
||||
html += '<option value="' + page.pgNr + '">';
|
||||
html += Functions.escapeHtml(page.pageDescr) + '</option>';
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(html);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.deletePage = function (pageId, callback) {
|
||||
DesignerOfflineDB.loadObject('pdf_pages', pageId, function (page) {
|
||||
if (page) {
|
||||
for (var i = 0; i < page.tblCords.length; i++) {
|
||||
DesignerOfflineDB.deleteObject('table_coords', page.tblCords[i]);
|
||||
}
|
||||
|
||||
DesignerOfflineDB.deleteObject('pdf_pages', pageId, callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.loadFirstPage = function (db, callback) {
|
||||
DesignerOfflineDB.loadAllObjects('pdf_pages', function (pages) {
|
||||
var firstPage = null;
|
||||
|
||||
for (var i = 0; i < pages.length; i++) {
|
||||
var page = pages[i];
|
||||
|
||||
if (page.dbName === db) {
|
||||
// give preference to a page having same name as the db
|
||||
if (page.pageDescr === db) {
|
||||
callback(page);
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstPage === null) {
|
||||
firstPage = page;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(firstPage);
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.showNewPageTables = function (check) {
|
||||
var allTables = $('#id_scroll_tab').find('td input:checkbox');
|
||||
allTables.prop('checked', check);
|
||||
|
||||
for (var tab = 0; tab < allTables.length; tab++) {
|
||||
var input = allTables[tab];
|
||||
|
||||
if (input.value) {
|
||||
var element = document.getElementById(input.value);
|
||||
element.style.top = DesignerPage.getRandom(550, 20) + 'px';
|
||||
element.style.left = DesignerPage.getRandom(700, 20) + 'px';
|
||||
DesignerMove.visibleTab(input, input.value);
|
||||
}
|
||||
}
|
||||
|
||||
selectedPage = -1;
|
||||
$('#page_name').text(Messages.strUntitled);
|
||||
DesignerMove.markUnsaved();
|
||||
};
|
||||
|
||||
DesignerPage.loadHtmlForPage = function (pageId) {
|
||||
DesignerPage.showNewPageTables(true);
|
||||
DesignerPage.loadPageObjects(pageId, function (page, tblCords) {
|
||||
$('#name-panel').find('#page_name').text(page.pageDescr);
|
||||
var tableMissing = false;
|
||||
|
||||
for (var t = 0; t < tblCords.length; t++) {
|
||||
var tbId = db + '.' + tblCords[t].tableName;
|
||||
var table = document.getElementById(tbId);
|
||||
|
||||
if (table === null) {
|
||||
tableMissing = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
table.style.top = tblCords[t].y + 'px';
|
||||
table.style.left = tblCords[t].x + 'px';
|
||||
var checkbox = document.getElementById('check_vis_' + tbId);
|
||||
checkbox.checked = true;
|
||||
DesignerMove.visibleTab(checkbox, checkbox.value);
|
||||
}
|
||||
|
||||
DesignerMove.markSaved();
|
||||
|
||||
if (tableMissing === true) {
|
||||
DesignerMove.markUnsaved();
|
||||
Functions.ajaxShowMessage(Messages.strSavedPageTableMissing);
|
||||
}
|
||||
|
||||
selectedPage = page.pgNr;
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.loadPageObjects = function (pageId, callback) {
|
||||
DesignerOfflineDB.loadObject('pdf_pages', pageId, function (page) {
|
||||
var tblCords = [];
|
||||
var count = page.tblCords.length;
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
DesignerOfflineDB.loadObject('table_coords', page.tblCords[i], function (tblCord) {
|
||||
tblCords.push(tblCord);
|
||||
|
||||
if (tblCords.length === count) {
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(page, tblCords);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.getRandom = function (max, min) {
|
||||
var val = Math.random() * (max - min) + min;
|
||||
return Math.floor(val);
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
AJAX.registerOnload('export_output.js', function () {
|
||||
$(document).on('keydown', function (e) {
|
||||
if ((e.which || e.keyCode) === 116) {
|
||||
e.preventDefault();
|
||||
$('#export_refresh_form').trigger('submit');
|
||||
}
|
||||
});
|
||||
$('.export_refresh_btn').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#export_refresh_form').trigger('submit');
|
||||
});
|
||||
});
|
@ -0,0 +1,368 @@
|
||||
/**
|
||||
* @fileoverview functions used in GIS data editor
|
||||
*
|
||||
* @requires jQuery
|
||||
*
|
||||
*/
|
||||
|
||||
/* global addZoomPanControllers, storeGisSvgRef, selectVisualization, styleOSM, zoomAndPan */
|
||||
// js/table/gis_visualization.js
|
||||
|
||||
/* global themeImagePath */
|
||||
// templates/javascript/variables.twig
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var gisEditorLoaded = false;
|
||||
/**
|
||||
* Closes the GIS data editor and perform necessary clean up work.
|
||||
*/
|
||||
|
||||
function closeGISEditor() {
|
||||
$('#popup_background').fadeOut('fast');
|
||||
$('#gis_editor').fadeOut('fast', function () {
|
||||
$(this).empty();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Prepares the HTML received via AJAX.
|
||||
*/
|
||||
|
||||
|
||||
function prepareJSVersion() {
|
||||
// Change the text on the submit button
|
||||
$('#gis_editor').find('input[name=\'gis_data[save]\']').val(Messages.strCopy).insertAfter($('#gis_data_textarea')).before('<br><br>'); // Add close and cancel links
|
||||
|
||||
$('#gis_data_editor').prepend('<a class="close_gis_editor" href="#">' + Messages.strClose + '</a>');
|
||||
$('<a class="cancel_gis_editor" href="#"> ' + Messages.strCancel + '</a>').insertAfter($('input[name=\'gis_data[save]\']')); // Remove the unnecessary text
|
||||
|
||||
$('div#gis_data_output p').remove(); // Remove 'add' buttons and add links
|
||||
|
||||
$('#gis_editor').find('input.add').each(function () {
|
||||
var $button = $(this);
|
||||
$button.addClass('addJs').removeClass('add');
|
||||
var classes = $button.attr('class');
|
||||
$button.replaceWith('<a class="' + classes + '" name="' + $button.attr('name') + '" href="#">+ ' + $button.val() + '</a>');
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Returns the HTML for a data point.
|
||||
*
|
||||
* @param {number} pointNumber point number
|
||||
* @param {string} prefix prefix of the name
|
||||
* @return {string} the HTML for a data point
|
||||
*/
|
||||
|
||||
|
||||
function addDataPoint(pointNumber, prefix) {
|
||||
return '<br>' + Functions.sprintf(Messages.strPointN, pointNumber + 1) + ': ' + '<label for="x">' + Messages.strX + '</label>' + '<input type="text" name="' + prefix + '[' + pointNumber + '][x]" value="">' + '<label for="y">' + Messages.strY + '</label>' + '<input type="text" name="' + prefix + '[' + pointNumber + '][y]" value="">';
|
||||
}
|
||||
/**
|
||||
* Initialize the visualization in the GIS data editor.
|
||||
*/
|
||||
|
||||
|
||||
function initGISEditorVisualization() {
|
||||
storeGisSvgRef(); // Loads either SVG or OSM visualization based on the choice
|
||||
|
||||
selectVisualization(); // Adds necessary styles to the div that contains the openStreetMap
|
||||
|
||||
styleOSM(); // Adds controllers for zooming and panning
|
||||
|
||||
addZoomPanControllers();
|
||||
zoomAndPan();
|
||||
}
|
||||
/**
|
||||
* Loads JavaScript files and the GIS editor.
|
||||
*
|
||||
* @param value current value of the geometry field
|
||||
* @param field field name
|
||||
* @param type geometry type
|
||||
* @param inputName name of the input field
|
||||
* @param token token
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
|
||||
function loadJSAndGISEditor(value, field, type, inputName) {
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var script;
|
||||
script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = 'js/dist/table/gis_visualization.js';
|
||||
head.appendChild(script); // OpenLayers.js is BIG and takes time. So asynchronous loading would not work.
|
||||
// Load the JS and do a callback to load the content for the GIS Editor.
|
||||
|
||||
script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
|
||||
script.onreadystatechange = function () {
|
||||
if (this.readyState === 'complete') {
|
||||
loadGISEditor(value, field, type, inputName);
|
||||
}
|
||||
};
|
||||
|
||||
script.onload = function () {
|
||||
loadGISEditor(value, field, type, inputName);
|
||||
};
|
||||
|
||||
script.onerror = function () {
|
||||
loadGISEditor(value, field, type, inputName);
|
||||
};
|
||||
|
||||
script.src = 'js/vendor/openlayers/OpenLayers.js';
|
||||
head.appendChild(script);
|
||||
gisEditorLoaded = true;
|
||||
}
|
||||
/**
|
||||
* Loads the GIS editor via AJAX
|
||||
*
|
||||
* @param value current value of the geometry field
|
||||
* @param field field name
|
||||
* @param type geometry type
|
||||
* @param inputName name of the input field
|
||||
*/
|
||||
|
||||
|
||||
function loadGISEditor(value, field, type, inputName) {
|
||||
var $gisEditor = $('#gis_editor');
|
||||
$.post('index.php?route=/gis-data-editor', {
|
||||
'field': field,
|
||||
'value': value,
|
||||
'type': type,
|
||||
'input_name': inputName,
|
||||
'get_gis_editor': true,
|
||||
'ajax_request': true,
|
||||
'server': CommonParams.get('server')
|
||||
}, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
$gisEditor.html(data.gis_editor);
|
||||
initGISEditorVisualization();
|
||||
prepareJSVersion();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
/**
|
||||
* Opens up the dialog for the GIS data editor.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
|
||||
function openGISEditor() {
|
||||
// Center the popup
|
||||
var windowWidth = document.documentElement.clientWidth;
|
||||
var windowHeight = document.documentElement.clientHeight;
|
||||
var popupWidth = windowWidth * 0.9;
|
||||
var popupHeight = windowHeight * 0.9;
|
||||
var popupOffsetTop = windowHeight / 2 - popupHeight / 2;
|
||||
var popupOffsetLeft = windowWidth / 2 - popupWidth / 2;
|
||||
var $gisEditor = $('#gis_editor');
|
||||
var $background = $('#popup_background');
|
||||
$gisEditor.css({
|
||||
'top': popupOffsetTop,
|
||||
'left': popupOffsetLeft,
|
||||
'width': popupWidth,
|
||||
'height': popupHeight
|
||||
});
|
||||
$background.css({
|
||||
'opacity': '0.7'
|
||||
});
|
||||
$gisEditor.append('<div id="gis_data_editor">' + '<img class="ajaxIcon" id="loadingMonitorIcon" src="' + themeImagePath + 'ajax_clock_small.gif" alt="">' + '</div>'); // Make it appear
|
||||
|
||||
$background.fadeIn('fast');
|
||||
$gisEditor.fadeIn('fast');
|
||||
}
|
||||
/**
|
||||
* Prepare and insert the GIS data in Well Known Text format
|
||||
* to the input field.
|
||||
*/
|
||||
|
||||
|
||||
function insertDataAndClose() {
|
||||
var $form = $('form#gis_data_editor_form');
|
||||
var inputName = $form.find('input[name=\'input_name\']').val();
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
$.post('index.php?route=/gis-data-editor', $form.serialize() + argsep + 'generate=true' + argsep + 'ajax_request=true', function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
$('input[name=\'' + inputName + '\']').val(data.result);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}, 'json');
|
||||
closeGISEditor();
|
||||
}
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
|
||||
|
||||
AJAX.registerTeardown('gis_data_editor.js', function () {
|
||||
$(document).off('click', '#gis_editor input[name=\'gis_data[save]\']');
|
||||
$(document).off('submit', '#gis_editor');
|
||||
$(document).off('change', '#gis_editor input[type=\'text\']');
|
||||
$(document).off('change', '#gis_editor select.gis_type');
|
||||
$(document).off('click', '#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor');
|
||||
$(document).off('click', '#gis_editor a.addJs.addPoint');
|
||||
$(document).off('click', '#gis_editor a.addLine.addJs');
|
||||
$(document).off('click', '#gis_editor a.addJs.addPolygon');
|
||||
$(document).off('click', '#gis_editor a.addJs.addGeom');
|
||||
});
|
||||
AJAX.registerOnload('gis_data_editor.js', function () {
|
||||
/**
|
||||
* Prepares and insert the GIS data to the input field on clicking 'copy'.
|
||||
*/
|
||||
$(document).on('click', '#gis_editor input[name=\'gis_data[save]\']', function (event) {
|
||||
event.preventDefault();
|
||||
insertDataAndClose();
|
||||
});
|
||||
/**
|
||||
* Prepares and insert the GIS data to the input field on pressing 'enter'.
|
||||
*/
|
||||
|
||||
$(document).on('submit', '#gis_editor', function (event) {
|
||||
event.preventDefault();
|
||||
insertDataAndClose();
|
||||
});
|
||||
/**
|
||||
* Trigger asynchronous calls on data change and update the output.
|
||||
*/
|
||||
|
||||
$(document).on('change', '#gis_editor input[type=\'text\']', function () {
|
||||
var $form = $('form#gis_data_editor_form');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
$.post('index.php?route=/gis-data-editor', $form.serialize() + argsep + 'generate=true' + argsep + 'ajax_request=true', function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
$('#gis_data_textarea').val(data.result);
|
||||
$('#placeholder').empty().removeClass('hasSVG').html(data.visualization);
|
||||
$('#openlayersmap').empty();
|
||||
/* TODO: the gis_data_editor should rather return JSON than JS code to eval */
|
||||
// eslint-disable-next-line no-eval
|
||||
|
||||
eval(data.openLayers);
|
||||
initGISEditorVisualization();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}, 'json');
|
||||
});
|
||||
/**
|
||||
* Update the form on change of the GIS type.
|
||||
*/
|
||||
|
||||
$(document).on('change', '#gis_editor select.gis_type', function () {
|
||||
var $gisEditor = $('#gis_editor');
|
||||
var $form = $('form#gis_data_editor_form');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
$.post('index.php?route=/gis-data-editor', $form.serialize() + argsep + 'get_gis_editor=true' + argsep + 'ajax_request=true', function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
$gisEditor.html(data.gis_editor);
|
||||
initGISEditorVisualization();
|
||||
prepareJSVersion();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}, 'json');
|
||||
});
|
||||
/**
|
||||
* Handles closing of the GIS data editor.
|
||||
*/
|
||||
|
||||
$(document).on('click', '#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor', function () {
|
||||
closeGISEditor();
|
||||
});
|
||||
/**
|
||||
* Handles adding data points
|
||||
*/
|
||||
|
||||
$(document).on('click', '#gis_editor a.addJs.addPoint', function () {
|
||||
var $a = $(this);
|
||||
var name = $a.attr('name'); // Eg. name = gis_data[0][MULTIPOINT][add_point] => prefix = gis_data[0][MULTIPOINT]
|
||||
|
||||
var prefix = name.substr(0, name.length - 11); // Find the number of points
|
||||
|
||||
var $noOfPointsInput = $('input[name=\'' + prefix + '[no_of_points]' + '\']');
|
||||
var noOfPoints = parseInt($noOfPointsInput.val(), 10); // Add the new data point
|
||||
|
||||
var html = addDataPoint(noOfPoints, prefix);
|
||||
$a.before(html);
|
||||
$noOfPointsInput.val(noOfPoints + 1);
|
||||
});
|
||||
/**
|
||||
* Handles adding linestrings and inner rings
|
||||
*/
|
||||
|
||||
$(document).on('click', '#gis_editor a.addLine.addJs', function () {
|
||||
var $a = $(this);
|
||||
var name = $a.attr('name'); // Eg. name = gis_data[0][MULTILINESTRING][add_line] => prefix = gis_data[0][MULTILINESTRING]
|
||||
|
||||
var prefix = name.substr(0, name.length - 10);
|
||||
var type = prefix.slice(prefix.lastIndexOf('[') + 1, prefix.lastIndexOf(']')); // Find the number of lines
|
||||
|
||||
var $noOfLinesInput = $('input[name=\'' + prefix + '[no_of_lines]' + '\']');
|
||||
var noOfLines = parseInt($noOfLinesInput.val(), 10); // Add the new linesting of inner ring based on the type
|
||||
|
||||
var html = '<br>';
|
||||
var noOfPoints;
|
||||
|
||||
if (type === 'MULTILINESTRING') {
|
||||
html += Messages.strLineString + ' ' + (noOfLines + 1) + ':';
|
||||
noOfPoints = 2;
|
||||
} else {
|
||||
html += Messages.strInnerRing + ' ' + noOfLines + ':';
|
||||
noOfPoints = 4;
|
||||
}
|
||||
|
||||
html += '<input type="hidden" name="' + prefix + '[' + noOfLines + '][no_of_points]" value="' + noOfPoints + '">';
|
||||
|
||||
for (var i = 0; i < noOfPoints; i++) {
|
||||
html += addDataPoint(i, prefix + '[' + noOfLines + ']');
|
||||
}
|
||||
|
||||
html += '<a class="addPoint addJs" name="' + prefix + '[' + noOfLines + '][add_point]" href="#">+ ' + Messages.strAddPoint + '</a><br>';
|
||||
$a.before(html);
|
||||
$noOfLinesInput.val(noOfLines + 1);
|
||||
});
|
||||
/**
|
||||
* Handles adding polygons
|
||||
*/
|
||||
|
||||
$(document).on('click', '#gis_editor a.addJs.addPolygon', function () {
|
||||
var $a = $(this);
|
||||
var name = $a.attr('name'); // Eg. name = gis_data[0][MULTIPOLYGON][add_polygon] => prefix = gis_data[0][MULTIPOLYGON]
|
||||
|
||||
var prefix = name.substr(0, name.length - 13); // Find the number of polygons
|
||||
|
||||
var $noOfPolygonsInput = $('input[name=\'' + prefix + '[no_of_polygons]' + '\']');
|
||||
var noOfPolygons = parseInt($noOfPolygonsInput.val(), 10); // Add the new polygon
|
||||
|
||||
var html = Messages.strPolygon + ' ' + (noOfPolygons + 1) + ':<br>';
|
||||
html += '<input type="hidden" name="' + prefix + '[' + noOfPolygons + '][no_of_lines]" value="1">' + '<br>' + Messages.strOuterRing + ':' + '<input type="hidden" name="' + prefix + '[' + noOfPolygons + '][0][no_of_points]" value="4">';
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
html += addDataPoint(i, prefix + '[' + noOfPolygons + '][0]');
|
||||
}
|
||||
|
||||
html += '<a class="addPoint addJs" name="' + prefix + '[' + noOfPolygons + '][0][add_point]" href="#">+ ' + Messages.strAddPoint + '</a><br>' + '<a class="addLine addJs" name="' + prefix + '[' + noOfPolygons + '][add_line]" href="#">+ ' + Messages.strAddInnerRing + '</a><br><br>';
|
||||
$a.before(html);
|
||||
$noOfPolygonsInput.val(noOfPolygons + 1);
|
||||
});
|
||||
/**
|
||||
* Handles adding geoms
|
||||
*/
|
||||
|
||||
$(document).on('click', '#gis_editor a.addJs.addGeom', function () {
|
||||
var $a = $(this);
|
||||
var prefix = 'gis_data[GEOMETRYCOLLECTION]'; // Find the number of geoms
|
||||
|
||||
var $noOfGeomsInput = $('input[name=\'' + prefix + '[geom_count]' + '\']');
|
||||
var noOfGeoms = parseInt($noOfGeomsInput.val(), 10);
|
||||
var html1 = Messages.strGeometry + ' ' + (noOfGeoms + 1) + ':<br>';
|
||||
var $geomType = $('select[name=\'gis_data[' + (noOfGeoms - 1) + '][gis_type]\']').clone();
|
||||
$geomType.attr('name', 'gis_data[' + noOfGeoms + '][gis_type]').val('POINT');
|
||||
var html2 = '<br>' + Messages.strPoint + ' :' + '<label for="x"> ' + Messages.strX + ' </label>' + '<input type="text" name="gis_data[' + noOfGeoms + '][POINT][x]" value="">' + '<label for="y"> ' + Messages.strY + ' </label>' + '<input type="text" name="gis_data[' + noOfGeoms + '][POINT][y]" value="">' + '<br><br>';
|
||||
$a.before(html1);
|
||||
$geomType.insertBefore($a);
|
||||
$a.before(html2);
|
||||
$noOfGeomsInput.val(noOfGeoms + 1);
|
||||
});
|
||||
});
|
@ -0,0 +1,795 @@
|
||||
/**
|
||||
* @fileoverview function used for index manipulation pages
|
||||
* @name Table Structure
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @required js/functions.js
|
||||
*/
|
||||
|
||||
/* global fulltextIndexes:writable, indexes:writable, primaryIndexes:writable, spatialIndexes:writable, uniqueIndexes:writable */
|
||||
// js/functions.js
|
||||
var Indexes = {};
|
||||
/**
|
||||
* Returns the array of indexes based on the index choice
|
||||
*
|
||||
* @param {string} indexChoice index choice
|
||||
*
|
||||
* @return {null|object}
|
||||
*/
|
||||
|
||||
Indexes.getIndexArray = function (indexChoice) {
|
||||
var sourceArray = null;
|
||||
|
||||
switch (indexChoice.toLowerCase()) {
|
||||
case 'primary':
|
||||
sourceArray = primaryIndexes;
|
||||
break;
|
||||
|
||||
case 'unique':
|
||||
sourceArray = uniqueIndexes;
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
sourceArray = indexes;
|
||||
break;
|
||||
|
||||
case 'fulltext':
|
||||
sourceArray = fulltextIndexes;
|
||||
break;
|
||||
|
||||
case 'spatial':
|
||||
sourceArray = spatialIndexes;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return sourceArray;
|
||||
};
|
||||
/**
|
||||
* Hides/shows the inputs and submits appropriately depending
|
||||
* on whether the index type chosen is 'SPATIAL' or not.
|
||||
*/
|
||||
|
||||
|
||||
Indexes.checkIndexType = function () {
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Dropdown to select the index choice.
|
||||
*/
|
||||
var $selectIndexChoice = $('#select_index_choice');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Dropdown to select the index type.
|
||||
*/
|
||||
|
||||
var $selectIndexType = $('#select_index_type');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Table header for the size column.
|
||||
*/
|
||||
|
||||
var $sizeHeader = $('#index_columns').find('thead tr').children('th').eq(1);
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Inputs to specify the columns for the index.
|
||||
*/
|
||||
|
||||
var $columnInputs = $('select[name="index[columns][names][]"]');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Inputs to specify sizes for columns of the index.
|
||||
*/
|
||||
|
||||
var $sizeInputs = $('input[name="index[columns][sub_parts][]"]');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Footer containing the controllers to add more columns
|
||||
*/
|
||||
|
||||
var $addMore = $('#index_frm').find('.add_more');
|
||||
|
||||
if ($selectIndexChoice.val() === 'SPATIAL') {
|
||||
// Disable and hide the size column
|
||||
$sizeHeader.hide();
|
||||
$sizeInputs.each(function () {
|
||||
$(this).prop('disabled', true).parent('td').hide();
|
||||
}); // Disable and hide the columns of the index other than the first one
|
||||
|
||||
var initial = true;
|
||||
$columnInputs.each(function () {
|
||||
var $columnInput = $(this);
|
||||
|
||||
if (!initial) {
|
||||
$columnInput.prop('disabled', true).parent('td').hide();
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
}); // Hide controllers to add more columns
|
||||
|
||||
$addMore.hide();
|
||||
} else {
|
||||
// Enable and show the size column
|
||||
$sizeHeader.show();
|
||||
$sizeInputs.each(function () {
|
||||
$(this).prop('disabled', false).parent('td').show();
|
||||
}); // Enable and show the columns of the index
|
||||
|
||||
$columnInputs.each(function () {
|
||||
$(this).prop('disabled', false).parent('td').show();
|
||||
}); // Show controllers to add more columns
|
||||
|
||||
$addMore.show();
|
||||
}
|
||||
|
||||
if ($selectIndexChoice.val() === 'SPATIAL' || $selectIndexChoice.val() === 'FULLTEXT') {
|
||||
$selectIndexType.val('').prop('disabled', true);
|
||||
} else {
|
||||
$selectIndexType.prop('disabled', false);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Sets current index information into form parameters.
|
||||
*
|
||||
* @param {any[]} sourceArray Array containing index columns
|
||||
* @param {string} indexChoice Choice of index
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
Indexes.setIndexFormParameters = function (sourceArray, indexChoice) {
|
||||
if (indexChoice === 'index') {
|
||||
$('input[name="indexes"]').val(JSON.stringify(sourceArray));
|
||||
} else {
|
||||
$('input[name="' + indexChoice + '_indexes"]').val(JSON.stringify(sourceArray));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Removes a column from an Index.
|
||||
*
|
||||
* @param {string} colIndex Index of column in form
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
Indexes.removeColumnFromIndex = function (colIndex) {
|
||||
// Get previous index details.
|
||||
var previousIndex = $('select[name="field_key[' + colIndex + ']"]').attr('data-index');
|
||||
|
||||
if (previousIndex.length) {
|
||||
previousIndex = previousIndex.split(',');
|
||||
var sourceArray = Indexes.getIndexArray(previousIndex[0]);
|
||||
|
||||
if (sourceArray === null) {
|
||||
return;
|
||||
} // Remove column from index array.
|
||||
|
||||
|
||||
var sourceLength = sourceArray[previousIndex[1]].columns.length;
|
||||
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
if (sourceArray[previousIndex[1]].columns[i].col_index === colIndex) {
|
||||
sourceArray[previousIndex[1]].columns.splice(i, 1);
|
||||
}
|
||||
} // Remove index completely if no columns left.
|
||||
|
||||
|
||||
if (sourceArray[previousIndex[1]].columns.length === 0) {
|
||||
sourceArray.splice(previousIndex[1], 1);
|
||||
} // Update current index details.
|
||||
|
||||
|
||||
$('select[name="field_key[' + colIndex + ']"]').attr('data-index', ''); // Update form index parameters.
|
||||
|
||||
Indexes.setIndexFormParameters(sourceArray, previousIndex[0].toLowerCase());
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Adds a column to an Index.
|
||||
*
|
||||
* @param {any[]} sourceArray Array holding corresponding indexes
|
||||
* @param {string} arrayIndex Index of an INDEX in array
|
||||
* @param {string} indexChoice Choice of Index
|
||||
* @param {string} colIndex Index of column on form
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
Indexes.addColumnToIndex = function (sourceArray, arrayIndex, indexChoice, colIndex) {
|
||||
if (colIndex >= 0) {
|
||||
// Remove column from other indexes (if any).
|
||||
Indexes.removeColumnFromIndex(colIndex);
|
||||
}
|
||||
|
||||
var indexName = $('input[name="index[Key_name]"]').val();
|
||||
var indexComment = $('input[name="index[Index_comment]"]').val();
|
||||
var keyBlockSize = $('input[name="index[Key_block_size]"]').val();
|
||||
var parser = $('input[name="index[Parser]"]').val();
|
||||
var indexType = $('select[name="index[Index_type]"]').val();
|
||||
var columns = [];
|
||||
$('#index_columns').find('tbody').find('tr').each(function () {
|
||||
// Get columns in particular order.
|
||||
var colIndex = $(this).find('select[name="index[columns][names][]"]').val();
|
||||
var size = $(this).find('input[name="index[columns][sub_parts][]"]').val();
|
||||
columns.push({
|
||||
'col_index': colIndex,
|
||||
'size': size
|
||||
});
|
||||
}); // Update or create an index.
|
||||
|
||||
sourceArray[arrayIndex] = {
|
||||
'Key_name': indexName,
|
||||
'Index_comment': indexComment,
|
||||
'Index_choice': indexChoice.toUpperCase(),
|
||||
'Key_block_size': keyBlockSize,
|
||||
'Parser': parser,
|
||||
'Index_type': indexType,
|
||||
'columns': columns
|
||||
}; // Display index name (or column list)
|
||||
|
||||
var displayName = indexName;
|
||||
|
||||
if (displayName === '') {
|
||||
var columnNames = [];
|
||||
$.each(columns, function () {
|
||||
columnNames.push($('input[name="field_name[' + this.col_index + ']"]').val());
|
||||
});
|
||||
displayName = '[' + columnNames.join(', ') + ']';
|
||||
}
|
||||
|
||||
$.each(columns, function () {
|
||||
var id = 'index_name_' + this.col_index + '_8';
|
||||
var $name = $('#' + id);
|
||||
|
||||
if ($name.length === 0) {
|
||||
$name = $('<a id="' + id + '" href="#" class="ajax show_index_dialog"></a>');
|
||||
$name.insertAfter($('select[name="field_key[' + this.col_index + ']"]'));
|
||||
}
|
||||
|
||||
var $text = $('<small>').text(displayName);
|
||||
$name.html($text);
|
||||
});
|
||||
|
||||
if (colIndex >= 0) {
|
||||
// Update index details on form.
|
||||
$('select[name="field_key[' + colIndex + ']"]').attr('data-index', indexChoice + ',' + arrayIndex);
|
||||
}
|
||||
|
||||
Indexes.setIndexFormParameters(sourceArray, indexChoice.toLowerCase());
|
||||
};
|
||||
/**
|
||||
* Get choices list for a column to create a composite index with.
|
||||
*
|
||||
* @param {any[]} sourceArray Array hodling columns for particular index
|
||||
* @param {string} colIndex Choice of index
|
||||
*
|
||||
* @return {JQuery} jQuery Object
|
||||
*/
|
||||
|
||||
|
||||
Indexes.getCompositeIndexList = function (sourceArray, colIndex) {
|
||||
// Remove any previous list.
|
||||
if ($('#composite_index_list').length) {
|
||||
$('#composite_index_list').remove();
|
||||
} // Html list.
|
||||
|
||||
|
||||
var $compositeIndexList = $('<ul id="composite_index_list">' + '<div>' + Messages.strCompositeWith + '</div>' + '</ul>'); // Add each column to list available for composite index.
|
||||
|
||||
var sourceLength = sourceArray.length;
|
||||
var alreadyPresent = false;
|
||||
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
var subArrayLen = sourceArray[i].columns.length;
|
||||
var columnNames = [];
|
||||
|
||||
for (var j = 0; j < subArrayLen; j++) {
|
||||
columnNames.push($('input[name="field_name[' + sourceArray[i].columns[j].col_index + ']"]').val());
|
||||
|
||||
if (colIndex === sourceArray[i].columns[j].col_index) {
|
||||
alreadyPresent = true;
|
||||
}
|
||||
}
|
||||
|
||||
$compositeIndexList.append('<li>' + '<input type="radio" name="composite_with" ' + (alreadyPresent ? 'checked="checked"' : '') + ' id="composite_index_' + i + '" value="' + i + '">' + '<label for="composite_index_' + i + '">' + columnNames.join(', ') + '</label>' + '</li>');
|
||||
}
|
||||
|
||||
return $compositeIndexList;
|
||||
};
|
||||
/**
|
||||
* Shows 'Add Index' dialog.
|
||||
*
|
||||
* @param {any[]} sourceArray Array holding particular index
|
||||
* @param {string} arrayIndex Index of an INDEX in array
|
||||
* @param {any[]} targetColumns Columns for an INDEX
|
||||
* @param {string} colIndex Index of column on form
|
||||
* @param {object} index Index detail object
|
||||
* @param {boolean} showDialog Whether to show index creation dialog or not
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
Indexes.showAddIndexDialog = function (sourceArray, arrayIndex, targetColumns, colIndex, index, showDialog) {
|
||||
var showDialogLocal = typeof showDialog !== 'undefined' ? showDialog : true; // Prepare post-data.
|
||||
|
||||
var $table = $('input[name="table"]');
|
||||
var table = $table.length > 0 ? $table.val() : '';
|
||||
var postData = {
|
||||
'server': CommonParams.get('server'),
|
||||
'db': $('input[name="db"]').val(),
|
||||
'table': table,
|
||||
'ajax_request': 1,
|
||||
'create_edit_table': 1,
|
||||
'index': index
|
||||
};
|
||||
var columns = {};
|
||||
|
||||
for (var i = 0; i < targetColumns.length; i++) {
|
||||
var columnName = $('input[name="field_name[' + targetColumns[i] + ']"]').val();
|
||||
var columnType = $('select[name="field_type[' + targetColumns[i] + ']"]').val().toLowerCase();
|
||||
columns[columnName] = [columnType, targetColumns[i]];
|
||||
}
|
||||
|
||||
postData.columns = JSON.stringify(columns);
|
||||
var buttonOptions = {};
|
||||
|
||||
buttonOptions[Messages.strGo] = function () {
|
||||
var isMissingValue = false;
|
||||
$('select[name="index[columns][names][]"]').each(function () {
|
||||
if ($(this).val() === '') {
|
||||
isMissingValue = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isMissingValue) {
|
||||
Indexes.addColumnToIndex(sourceArray, arrayIndex, index.Index_choice, colIndex);
|
||||
} else {
|
||||
Functions.ajaxShowMessage('<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt=""' + ' class="icon ic_s_error"> ' + Messages.strMissingColumn + ' </div>', false);
|
||||
return false;
|
||||
}
|
||||
|
||||
$(this).remove();
|
||||
};
|
||||
|
||||
buttonOptions[Messages.strCancel] = function () {
|
||||
if (colIndex >= 0) {
|
||||
// Handle state on 'Cancel'.
|
||||
var $selectList = $('select[name="field_key[' + colIndex + ']"]');
|
||||
|
||||
if (!$selectList.attr('data-index').length) {
|
||||
$selectList.find('option[value*="none"]').attr('selected', 'selected');
|
||||
} else {
|
||||
var previousIndex = $selectList.attr('data-index').split(',');
|
||||
$selectList.find('option[value*="' + previousIndex[0].toLowerCase() + '"]').attr('selected', 'selected');
|
||||
}
|
||||
}
|
||||
|
||||
$(this).dialog('close');
|
||||
};
|
||||
|
||||
var $msgbox = Functions.ajaxShowMessage();
|
||||
$.post('index.php?route=/table/indexes', postData, function (data) {
|
||||
if (data.success === false) {
|
||||
// in the case of an error, show the error message returned.
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
} else {
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
var $div = $('<div></div>');
|
||||
|
||||
if (showDialogLocal) {
|
||||
// Show dialog if the request was successful
|
||||
if ($('#addIndex').length > 0) {
|
||||
$('#addIndex').remove();
|
||||
}
|
||||
|
||||
$div.append(data.message).dialog({
|
||||
title: Messages.strAddIndex,
|
||||
width: 450,
|
||||
minHeight: 250,
|
||||
create: function () {
|
||||
$(this).on('keypress', function (e) {
|
||||
if (e.which === 13 || e.keyCode === 13 || window.event.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
buttonOptions[Messages.strGo]();
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
open: function () {
|
||||
Functions.checkIndexName('index_frm');
|
||||
Functions.showHints($div);
|
||||
$('#index_columns').find('td').each(function () {
|
||||
$(this).css('width', $(this).width() + 'px');
|
||||
});
|
||||
$('#index_columns').find('tbody').sortable({
|
||||
axis: 'y',
|
||||
containment: $('#index_columns').find('tbody'),
|
||||
tolerance: 'pointer'
|
||||
});
|
||||
},
|
||||
modal: true,
|
||||
buttons: buttonOptions,
|
||||
close: function () {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$div.append(data.message);
|
||||
$div.css({
|
||||
'display': 'none'
|
||||
});
|
||||
$div.appendTo($('body'));
|
||||
$div.attr({
|
||||
'id': 'addIndex'
|
||||
});
|
||||
var isMissingValue = false;
|
||||
$('select[name="index[columns][names][]"]').each(function () {
|
||||
if ($(this).val() === '') {
|
||||
isMissingValue = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isMissingValue) {
|
||||
Indexes.addColumnToIndex(sourceArray, arrayIndex, index.Index_choice, colIndex);
|
||||
} else {
|
||||
Functions.ajaxShowMessage('<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt=""' + ' class="icon ic_s_error"> ' + Messages.strMissingColumn + ' </div>', false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Creates a advanced index type selection dialog.
|
||||
*
|
||||
* @param {any[]} sourceArray Array holding a particular type of indexes
|
||||
* @param {string} indexChoice Choice of index
|
||||
* @param {string} colIndex Index of new column on form
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
Indexes.indexTypeSelectionDialog = function (sourceArray, indexChoice, colIndex) {
|
||||
var $singleColumnRadio = $('<input type="radio" id="single_column" name="index_choice"' + ' checked="checked">' + '<label for="single_column">' + Messages.strCreateSingleColumnIndex + '</label>');
|
||||
var $compositeIndexRadio = $('<input type="radio" id="composite_index"' + ' name="index_choice">' + '<label for="composite_index">' + Messages.strCreateCompositeIndex + '</label>');
|
||||
var $dialogContent = $('<fieldset class="pma-fieldset" id="advance_index_creator"></fieldset>');
|
||||
$dialogContent.append('<legend>' + indexChoice.toUpperCase() + '</legend>'); // For UNIQUE/INDEX type, show choice for single-column and composite index.
|
||||
|
||||
$dialogContent.append($singleColumnRadio);
|
||||
$dialogContent.append($compositeIndexRadio);
|
||||
var buttonOptions = {}; // 'OK' operation.
|
||||
|
||||
buttonOptions[Messages.strGo] = function () {
|
||||
if ($('#single_column').is(':checked')) {
|
||||
var index = {
|
||||
'Key_name': indexChoice === 'primary' ? 'PRIMARY' : '',
|
||||
'Index_choice': indexChoice.toUpperCase()
|
||||
};
|
||||
Indexes.showAddIndexDialog(sourceArray, sourceArray.length, [colIndex], colIndex, index);
|
||||
}
|
||||
|
||||
if ($('#composite_index').is(':checked')) {
|
||||
if ($('input[name="composite_with"]').length !== 0 && $('input[name="composite_with"]:checked').length === 0) {
|
||||
Functions.ajaxShowMessage('<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title=""' + ' alt="" class="icon ic_s_error"> ' + Messages.strFormEmpty + ' </div>', false);
|
||||
return false;
|
||||
}
|
||||
|
||||
var arrayIndex = $('input[name="composite_with"]:checked').val();
|
||||
var sourceLength = sourceArray[arrayIndex].columns.length;
|
||||
var targetColumns = [];
|
||||
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
targetColumns.push(sourceArray[arrayIndex].columns[i].col_index);
|
||||
}
|
||||
|
||||
targetColumns.push(colIndex);
|
||||
Indexes.showAddIndexDialog(sourceArray, arrayIndex, targetColumns, colIndex, sourceArray[arrayIndex]);
|
||||
}
|
||||
|
||||
$(this).remove();
|
||||
};
|
||||
|
||||
buttonOptions[Messages.strCancel] = function () {
|
||||
// Handle state on 'Cancel'.
|
||||
var $selectList = $('select[name="field_key[' + colIndex + ']"]');
|
||||
|
||||
if (!$selectList.attr('data-index').length) {
|
||||
$selectList.find('option[value*="none"]').attr('selected', 'selected');
|
||||
} else {
|
||||
var previousIndex = $selectList.attr('data-index').split(',');
|
||||
$selectList.find('option[value*="' + previousIndex[0].toLowerCase() + '"]').attr('selected', 'selected');
|
||||
}
|
||||
|
||||
$(this).remove();
|
||||
};
|
||||
|
||||
$('<div></div>').append($dialogContent).dialog({
|
||||
minWidth: 525,
|
||||
minHeight: 200,
|
||||
modal: true,
|
||||
title: Messages.strAddIndex,
|
||||
resizable: false,
|
||||
buttons: buttonOptions,
|
||||
open: function () {
|
||||
$('#composite_index').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$dialogContent.append(Indexes.getCompositeIndexList(sourceArray, colIndex));
|
||||
}
|
||||
});
|
||||
$('#single_column').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
if ($('#composite_index_list').length) {
|
||||
$('#composite_index_list').remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
close: function () {
|
||||
$('#composite_index').off('change');
|
||||
$('#single_column').off('change');
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
|
||||
|
||||
AJAX.registerTeardown('indexes.js', function () {
|
||||
$(document).off('click', '#save_index_frm');
|
||||
$(document).off('click', '#preview_index_frm');
|
||||
$(document).off('change', '#select_index_choice');
|
||||
$(document).off('click', 'a.drop_primary_key_index_anchor.ajax');
|
||||
$(document).off('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax');
|
||||
$(document).off('click', '#table_index tbody tr td.rename_index.ajax');
|
||||
$(document).off('click', '#index_frm input[type=submit]');
|
||||
$('body').off('change', 'select[name*="field_key"]');
|
||||
$(document).off('click', '.show_index_dialog');
|
||||
});
|
||||
/**
|
||||
* @description <p>Ajax scripts for table index page</p>
|
||||
*
|
||||
* Actions ajaxified here:
|
||||
* <ul>
|
||||
* <li>Showing/hiding inputs depending on the index type chosen</li>
|
||||
* <li>create/edit/drop indexes</li>
|
||||
* </ul>
|
||||
*/
|
||||
|
||||
AJAX.registerOnload('indexes.js', function () {
|
||||
// Re-initialize variables.
|
||||
primaryIndexes = [];
|
||||
uniqueIndexes = [];
|
||||
indexes = [];
|
||||
fulltextIndexes = [];
|
||||
spatialIndexes = []; // for table creation form
|
||||
|
||||
var $engineSelector = $('.create_table_form select[name=tbl_storage_engine]');
|
||||
|
||||
if ($engineSelector.length) {
|
||||
Functions.hideShowConnection($engineSelector);
|
||||
}
|
||||
|
||||
var $form = $('#index_frm');
|
||||
|
||||
if ($form.length > 0) {
|
||||
Functions.showIndexEditDialog($form);
|
||||
}
|
||||
|
||||
$(document).on('click', '#save_index_frm', function (event) {
|
||||
event.preventDefault();
|
||||
var $form = $('#index_frm');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var submitData = $form.serialize() + argsep + 'do_save_data=1' + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true';
|
||||
Functions.ajaxShowMessage(Messages.strProcessingRequest);
|
||||
AJAX.source = $form;
|
||||
$.post($form.attr('action'), submitData, AJAX.responseHandler);
|
||||
});
|
||||
$(document).on('click', '#preview_index_frm', function (event) {
|
||||
event.preventDefault();
|
||||
Functions.previewSql($('#index_frm'));
|
||||
});
|
||||
$(document).on('change', '#select_index_choice', function (event) {
|
||||
event.preventDefault();
|
||||
Indexes.checkIndexType();
|
||||
Functions.checkIndexName('index_frm');
|
||||
});
|
||||
/**
|
||||
* Ajax Event handler for 'Drop Index'
|
||||
*/
|
||||
|
||||
$(document).on('click', 'a.drop_primary_key_index_anchor.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var $anchor = $(this);
|
||||
/**
|
||||
* @var $currRow Object containing reference to the current field's row
|
||||
*/
|
||||
|
||||
var $currRow = $anchor.parents('tr');
|
||||
/** @var {number} rows Number of columns in the key */
|
||||
|
||||
var rows = $anchor.parents('td').attr('rowspan') || 1;
|
||||
/** @var {number} $rowsToHide Rows that should be hidden */
|
||||
|
||||
var $rowsToHide = $currRow;
|
||||
|
||||
for (var i = 1, $lastRow = $currRow.next(); i < rows; i++, $lastRow = $lastRow.next()) {
|
||||
$rowsToHide = $rowsToHide.add($lastRow);
|
||||
}
|
||||
|
||||
var question = $currRow.children('td').children('.drop_primary_key_index_msg').val();
|
||||
Functions.confirmPreviewSql(question, $anchor.attr('href'), function (url) {
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strDroppingPrimaryKeyIndex, false);
|
||||
var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData());
|
||||
$.post(url, params, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
var $tableRef = $rowsToHide.closest('table');
|
||||
|
||||
if ($rowsToHide.length === $tableRef.find('tbody > tr').length) {
|
||||
// We are about to remove all rows from the table
|
||||
$tableRef.hide('medium', function () {
|
||||
$('div.no_indexes_defined').show('medium');
|
||||
$rowsToHide.remove();
|
||||
});
|
||||
$tableRef.siblings('.alert-primary').hide('medium');
|
||||
} else {
|
||||
// We are removing some of the rows only
|
||||
$rowsToHide.hide('medium', function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
if ($('.result_query').length) {
|
||||
$('.result_query').remove();
|
||||
}
|
||||
|
||||
if (data.sql_query) {
|
||||
$('<div class="result_query"></div>').html(data.sql_query).prependTo('#structure_content');
|
||||
Functions.highlightSql($('#page_content'));
|
||||
}
|
||||
|
||||
Navigation.reload();
|
||||
CommonActions.refreshMain('index.php?route=/table/structure');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(Messages.strErrorProcessingRequest + ' : ' + data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
});
|
||||
}); // end Drop Primary Key/Index
|
||||
|
||||
/**
|
||||
* Ajax event handler for index edit
|
||||
**/
|
||||
|
||||
$(document).on('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var url;
|
||||
var title;
|
||||
|
||||
if ($(this).find('a').length === 0) {
|
||||
// Add index
|
||||
var valid = Functions.checkFormElementInRange($(this).closest('form')[0], 'added_fields', 'Column count has to be larger than zero.');
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
url = $(this).closest('form').serialize();
|
||||
title = Messages.strAddIndex;
|
||||
} else {
|
||||
// Edit index
|
||||
url = $(this).find('a').getPostData();
|
||||
title = Messages.strEditIndex;
|
||||
}
|
||||
|
||||
url += CommonParams.get('arg_separator') + 'ajax_request=true';
|
||||
Functions.indexEditorDialog(url, title, function (data) {
|
||||
CommonParams.set('db', data.params.db);
|
||||
CommonParams.set('table', data.params.table);
|
||||
CommonActions.refreshMain('index.php?route=/table/structure');
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Ajax event handler for index rename
|
||||
**/
|
||||
|
||||
$(document).on('click', '#table_index tbody tr td.rename_index.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var url = $(this).find('a').getPostData();
|
||||
var title = Messages.strRenameIndex;
|
||||
url += CommonParams.get('arg_separator') + 'ajax_request=true';
|
||||
Functions.indexRenameDialog(url, title, function (data) {
|
||||
CommonParams.set('db', data.params.db);
|
||||
CommonParams.set('table', data.params.table);
|
||||
CommonActions.refreshMain('index.php?route=/table/structure');
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Ajax event handler for advanced index creation during table creation
|
||||
* and column addition.
|
||||
*/
|
||||
|
||||
$('body').on('change', 'select[name*="field_key"]', function (e, showDialog) {
|
||||
var showDialogLocal = typeof showDialog !== 'undefined' ? showDialog : true; // Index of column on Table edit and create page.
|
||||
|
||||
var colIndex = /\d+/.exec($(this).attr('name'));
|
||||
colIndex = colIndex[0]; // Choice of selected index.
|
||||
|
||||
var indexChoice = /[a-z]+/.exec($(this).val());
|
||||
indexChoice = indexChoice[0]; // Array containing corresponding indexes.
|
||||
|
||||
var sourceArray = null;
|
||||
|
||||
if (indexChoice === 'none') {
|
||||
Indexes.removeColumnFromIndex(colIndex);
|
||||
var id = 'index_name_' + '0' + '_8';
|
||||
var $name = $('#' + id);
|
||||
|
||||
if ($name.length === 0) {
|
||||
$name = $('<a id="' + id + '" href="#" class="ajax show_index_dialog"></a>');
|
||||
$name.insertAfter($('select[name="field_key[' + '0' + ']"]'));
|
||||
}
|
||||
|
||||
$name.html('');
|
||||
return false;
|
||||
} // Select a source array.
|
||||
|
||||
|
||||
sourceArray = Indexes.getIndexArray(indexChoice);
|
||||
|
||||
if (sourceArray === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceArray.length === 0) {
|
||||
var index = {
|
||||
'Key_name': indexChoice === 'primary' ? 'PRIMARY' : '',
|
||||
'Index_choice': indexChoice.toUpperCase()
|
||||
};
|
||||
Indexes.showAddIndexDialog(sourceArray, 0, [colIndex], colIndex, index, showDialogLocal);
|
||||
} else {
|
||||
if (indexChoice === 'primary') {
|
||||
var arrayIndex = 0;
|
||||
var sourceLength = sourceArray[arrayIndex].columns.length;
|
||||
var targetColumns = [];
|
||||
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
targetColumns.push(sourceArray[arrayIndex].columns[i].col_index);
|
||||
}
|
||||
|
||||
targetColumns.push(colIndex);
|
||||
Indexes.showAddIndexDialog(sourceArray, arrayIndex, targetColumns, colIndex, sourceArray[arrayIndex], showDialogLocal);
|
||||
} else {
|
||||
// If there are multiple columns selected for an index, show advanced dialog.
|
||||
Indexes.indexTypeSelectionDialog(sourceArray, indexChoice, colIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
$(document).on('click', '.show_index_dialog', function (e) {
|
||||
e.preventDefault(); // Get index details.
|
||||
|
||||
var previousIndex = $(this).prev('select').attr('data-index').split(',');
|
||||
var indexChoice = previousIndex[0];
|
||||
var arrayIndex = previousIndex[1];
|
||||
var sourceArray = Indexes.getIndexArray(indexChoice);
|
||||
|
||||
if (sourceArray !== null) {
|
||||
var sourceLength = sourceArray[arrayIndex].columns.length;
|
||||
var targetColumns = [];
|
||||
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
targetColumns.push(sourceArray[arrayIndex].columns[i].col_index);
|
||||
}
|
||||
|
||||
Indexes.showAddIndexDialog(sourceArray, arrayIndex, targetColumns, -1, sourceArray[arrayIndex]);
|
||||
}
|
||||
});
|
||||
$('#index_frm').on('submit', function () {
|
||||
if (typeof this.elements['index[Key_name]'].disabled !== 'undefined') {
|
||||
this.elements['index[Key_name]'].disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Handles the resizing of a menu according to the available screen width
|
||||
*
|
||||
* Uses themes/original/css/resizable-menu.css.php
|
||||
*
|
||||
* To initialize:
|
||||
* $('#myMenu').menuResizer(function () {
|
||||
* // This function will be called to find out how much
|
||||
* // available horizontal space there is for the menu
|
||||
* return $('body').width() - 5; // Some extra margin for good measure
|
||||
* });
|
||||
*
|
||||
* To trigger a resize operation:
|
||||
* $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize()
|
||||
*
|
||||
* To restore the menu to a state like before it was initialized:
|
||||
* $('#myMenu').menuResizer('destroy');
|
||||
*
|
||||
* @package PhpMyAdmin
|
||||
*/
|
||||
(function ($) {
|
||||
function MenuResizer($container, widthCalculator) {
|
||||
var self = this;
|
||||
self.$container = $container;
|
||||
self.widthCalculator = widthCalculator;
|
||||
var windowWidth = $(window).width();
|
||||
|
||||
if (windowWidth < 768) {
|
||||
$('#pma_navigation_resizer').css({
|
||||
'width': '0px'
|
||||
});
|
||||
} // create submenu container
|
||||
|
||||
|
||||
var link = $('<a></a>', {
|
||||
'href': '#',
|
||||
'class': 'nav-link dropdown-toggle',
|
||||
'id': 'navbarDropdown',
|
||||
'role': 'button',
|
||||
'data-bs-toggle': 'dropdown',
|
||||
'aria-haspopup': 'true',
|
||||
'aria-expanded': 'false'
|
||||
}).text(Messages.strMore);
|
||||
var img = $container.find('li img');
|
||||
|
||||
if (img.length) {
|
||||
$(Functions.getImage('b_more').toString()).prependTo(link);
|
||||
}
|
||||
|
||||
var $submenu = $('<li></li>', {
|
||||
'class': 'nav-item dropdown d-none'
|
||||
}).append(link).append($('<ul></ul>', {
|
||||
'class': 'dropdown-menu dropdown-menu-end',
|
||||
'aria-labelledby': 'navbarDropdown'
|
||||
}));
|
||||
$container.append($submenu);
|
||||
setTimeout(function () {
|
||||
self.resize();
|
||||
}, 4);
|
||||
}
|
||||
|
||||
MenuResizer.prototype.resize = function () {
|
||||
var wmax = this.widthCalculator.call(this.$container);
|
||||
var windowWidth = $(window).width();
|
||||
var $submenu = this.$container.find('.nav-item.dropdown').last();
|
||||
var submenuW = $submenu.outerWidth(true);
|
||||
var $submenuUl = $submenu.find('.dropdown-menu');
|
||||
var $li = this.$container.find('> li');
|
||||
var $li2 = $submenuUl.find('.dropdown-item');
|
||||
var moreShown = $li2.length > 0; // Calculate the total width used by all the shown tabs
|
||||
|
||||
var totalLen = moreShown ? submenuW : 0;
|
||||
var l = $li.length - 1;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
totalLen += $($li[i]).outerWidth(true);
|
||||
} // eslint-disable-next-line compat/compat
|
||||
|
||||
|
||||
var hasVScroll = document.body.scrollHeight > document.body.clientHeight;
|
||||
|
||||
if (hasVScroll) {
|
||||
windowWidth += 15;
|
||||
}
|
||||
|
||||
if (windowWidth < 768) {
|
||||
wmax = 2000;
|
||||
} // Now hide menu elements that don't fit into the menubar
|
||||
|
||||
|
||||
var hidden = false; // Whether we have hidden any tabs
|
||||
|
||||
while (totalLen >= wmax && --l >= 0) {
|
||||
// Process the tabs backwards
|
||||
hidden = true;
|
||||
var el = $($li[l]);
|
||||
el.removeClass('nav-item').addClass('dropdown-item');
|
||||
var elWidth = el.outerWidth(true);
|
||||
el.data('width', elWidth);
|
||||
|
||||
if (!moreShown) {
|
||||
totalLen -= elWidth;
|
||||
el.prependTo($submenuUl);
|
||||
totalLen += submenuW;
|
||||
moreShown = true;
|
||||
} else {
|
||||
totalLen -= elWidth;
|
||||
el.prependTo($submenuUl);
|
||||
}
|
||||
} // If we didn't hide any tabs, then there might be some space to show some
|
||||
|
||||
|
||||
if (!hidden) {
|
||||
// Show menu elements that do fit into the menubar
|
||||
for (i = 0, l = $li2.length; i < l; i++) {
|
||||
totalLen += $($li2[i]).data('width'); // item fits or (it is the last item
|
||||
// and it would fit if More got removed)
|
||||
|
||||
if (totalLen < wmax || i === $li2.length - 1 && totalLen - submenuW < wmax) {
|
||||
$($li2[i]).removeClass('dropdown-item').addClass('nav-item');
|
||||
$($li2[i]).insertBefore($submenu);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // Show/hide the "More" tab as needed
|
||||
|
||||
|
||||
if (windowWidth < 768) {
|
||||
$('.navbar-collapse').css({
|
||||
'width': windowWidth - 80 - $('#pma_navigation').width()
|
||||
});
|
||||
$submenu.addClass('d-none');
|
||||
$('.navbar-collapse').css({
|
||||
'overflow': 'hidden'
|
||||
});
|
||||
} else {
|
||||
$('.navbar-collapse').css({
|
||||
'width': 'auto'
|
||||
});
|
||||
$('.navbar-collapse').css({
|
||||
'overflow': 'visible'
|
||||
});
|
||||
|
||||
if ($submenuUl.find('li').length > 0) {
|
||||
$submenu.removeClass('d-none');
|
||||
} else {
|
||||
$submenu.addClass('d-none');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MenuResizer.prototype.destroy = function () {
|
||||
var $submenu = this.$container.find('.nav-item.dropdown').removeData();
|
||||
$submenu.find('li').appendTo(this.$container);
|
||||
$submenu.remove();
|
||||
};
|
||||
/** Public API */
|
||||
|
||||
|
||||
var methods = {
|
||||
init: function (widthCalculator) {
|
||||
return this.each(function () {
|
||||
var $this = $(this);
|
||||
|
||||
if (!$this.data('menuResizer')) {
|
||||
$this.data('menuResizer', new MenuResizer($this, widthCalculator));
|
||||
}
|
||||
});
|
||||
},
|
||||
resize: function () {
|
||||
return this.each(function () {
|
||||
var self = $(this).data('menuResizer');
|
||||
|
||||
if (self) {
|
||||
self.resize();
|
||||
}
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
return this.each(function () {
|
||||
var self = $(this).data('menuResizer');
|
||||
|
||||
if (self) {
|
||||
self.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Extend jQuery
|
||||
*
|
||||
* @param {string} method
|
||||
*
|
||||
* @return {any}
|
||||
*/
|
||||
|
||||
$.fn.menuResizer = function (method) {
|
||||
if (methods[method]) {
|
||||
return methods[method].call(this);
|
||||
} else if (typeof method === 'function') {
|
||||
return methods.init.apply(this, [method]);
|
||||
} else {
|
||||
$.error('Method ' + method + ' does not exist on jQuery.menuResizer');
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
@ -0,0 +1,455 @@
|
||||
/**
|
||||
* @fileoverview functions used in server privilege pages
|
||||
* @name Server Privileges
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @requires js/functions.js
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates the "add a user" form
|
||||
*
|
||||
* @param theForm
|
||||
*
|
||||
* @return {bool} whether the form is validated or not
|
||||
*/
|
||||
function checkAddUser(theForm) {
|
||||
if (theForm.elements.hostname.value === '') {
|
||||
alert(Messages.strHostEmpty);
|
||||
theForm.elements.hostname.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (theForm.elements.pred_username && theForm.elements.pred_username.value === 'userdefined' && theForm.elements.username.value === '') {
|
||||
alert(Messages.strUserEmpty);
|
||||
theForm.elements.username.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return Functions.checkPassword($(theForm));
|
||||
}
|
||||
/**
|
||||
* Export privileges modal handler
|
||||
*
|
||||
* @param {object} data
|
||||
*
|
||||
* @param {JQuery} msgbox
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
function exportPrivilegesModalHandler(data, msgbox) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
var modal = $('#exportPrivilegesModal'); // Remove any previous privilege modal data, if any
|
||||
|
||||
modal.find('.modal-body').first().html('');
|
||||
$('#exportPrivilegesModalLabel').first().html('Loading');
|
||||
modal.modal('show');
|
||||
modal.on('shown.bs.modal', function () {
|
||||
modal.find('.modal-body').first().html(data.message);
|
||||
$('#exportPrivilegesModalLabel').first().html(data.title);
|
||||
Functions.ajaxRemoveMessage(msgbox); // Attach syntax highlighted editor to export dialog
|
||||
|
||||
Functions.getSqlEditor(modal.find('textarea'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
/**
|
||||
* @implements EventListener
|
||||
*/
|
||||
|
||||
|
||||
const EditUserGroup = {
|
||||
/**
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
handleEvent: function (event) {
|
||||
const editUserGroupModal = document.getElementById('editUserGroupModal');
|
||||
const button = event.relatedTarget;
|
||||
const username = button.getAttribute('data-username');
|
||||
$.get('index.php?route=/server/user-groups/edit-form', {
|
||||
'username': username,
|
||||
'server': CommonParams.get('server')
|
||||
}, data => {
|
||||
if (typeof data === 'undefined' || data.success !== true) {
|
||||
Functions.ajaxShowMessage(data.error, false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = bootstrap.Modal.getInstance(editUserGroupModal);
|
||||
const modalBody = editUserGroupModal.querySelector('.modal-body');
|
||||
const saveButton = editUserGroupModal.querySelector('#editUserGroupModalSaveButton');
|
||||
modalBody.innerHTML = data.message;
|
||||
saveButton.addEventListener('click', () => {
|
||||
const form = $(editUserGroupModal.querySelector('#changeUserGroupForm'));
|
||||
$.post('index.php?route=/server/privileges', form.serialize() + CommonParams.get('arg_separator') + 'ajax_request=1', data => {
|
||||
if (typeof data === 'undefined' || data.success !== true) {
|
||||
Functions.ajaxShowMessage(data.error, false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const userGroup = form.serializeArray().find(el => el.name === 'userGroup').value; // button -> td -> tr -> td.usrGroup
|
||||
|
||||
const userGroupTableCell = button.parentElement.parentElement.querySelector('.usrGroup');
|
||||
userGroupTableCell.textContent = userGroup;
|
||||
});
|
||||
modal.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @implements EventListener
|
||||
*/
|
||||
|
||||
const AccountLocking = {
|
||||
handleEvent: function () {
|
||||
const button = this;
|
||||
const isLocked = button.dataset.isLocked === 'true';
|
||||
const url = isLocked ? 'index.php?route=/server/privileges/account-unlock' : 'index.php?route=/server/privileges/account-lock';
|
||||
const params = {
|
||||
'username': button.dataset.userName,
|
||||
'hostname': button.dataset.hostName,
|
||||
'ajax_request': true,
|
||||
'server': CommonParams.get('server')
|
||||
};
|
||||
$.post(url, params, data => {
|
||||
if (data.success === false) {
|
||||
Functions.ajaxShowMessage(data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLocked) {
|
||||
const lockIcon = Functions.getImage('s_lock', Messages.strLock, {}).toString();
|
||||
button.innerHTML = '<span class="text-nowrap">' + lockIcon + ' ' + Messages.strLock + '</span>';
|
||||
button.title = Messages.strLockAccount;
|
||||
button.dataset.isLocked = 'false';
|
||||
} else {
|
||||
const unlockIcon = Functions.getImage('s_unlock', Messages.strUnlock, {}).toString();
|
||||
button.innerHTML = '<span class="text-nowrap">' + unlockIcon + ' ' + Messages.strUnlock + '</span>';
|
||||
button.title = Messages.strUnlockAccount;
|
||||
button.dataset.isLocked = 'true';
|
||||
}
|
||||
|
||||
Functions.ajaxShowMessage(data.message);
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* AJAX scripts for /server/privileges page.
|
||||
*
|
||||
* Actions ajaxified here:
|
||||
* Add user
|
||||
* Revoke a user
|
||||
* Edit privileges
|
||||
* Export privileges
|
||||
* Paginate table of users
|
||||
* Flush privileges
|
||||
*
|
||||
* @memberOf jQuery
|
||||
* @name document.ready
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
|
||||
AJAX.registerTeardown('server/privileges.js', function () {
|
||||
$('#fieldset_add_user_login').off('change', 'input[name=\'username\']');
|
||||
$(document).off('click', '#deleteUserCard .btn.ajax');
|
||||
const editUserGroupModal = document.getElementById('editUserGroupModal');
|
||||
|
||||
if (editUserGroupModal) {
|
||||
editUserGroupModal.removeEventListener('show.bs.modal', EditUserGroup);
|
||||
}
|
||||
|
||||
$(document).off('click', 'button.mult_submit[value=export]');
|
||||
$(document).off('click', 'a.export_user_anchor.ajax');
|
||||
$('button.jsAccountLocking').off('click');
|
||||
$('#dropUsersDbCheckbox').off('click');
|
||||
$(document).off('click', '.checkall_box');
|
||||
$(document).off('change', '#checkbox_SSL_priv');
|
||||
$(document).off('change', 'input[name="ssl_type"]');
|
||||
$(document).off('change', '#select_authentication_plugin');
|
||||
});
|
||||
AJAX.registerOnload('server/privileges.js', function () {
|
||||
/**
|
||||
* Display a warning if there is already a user by the name entered as the username.
|
||||
*/
|
||||
$('#fieldset_add_user_login').on('change', 'input[name=\'username\']', function () {
|
||||
var username = $(this).val();
|
||||
var $warning = $('#user_exists_warning');
|
||||
|
||||
if ($('#select_pred_username').val() === 'userdefined' && username !== '') {
|
||||
var href = $('form[name=\'usersForm\']').attr('action');
|
||||
var params = {
|
||||
'ajax_request': true,
|
||||
'server': CommonParams.get('server'),
|
||||
'validate_username': true,
|
||||
'username': username
|
||||
};
|
||||
$.get(href, params, function (data) {
|
||||
if (data.user_exists) {
|
||||
$warning.show();
|
||||
} else {
|
||||
$warning.hide();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$warning.hide();
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Indicating password strength
|
||||
*/
|
||||
|
||||
$('#text_pma_pw').on('keyup', function () {
|
||||
var meterObj = $('#password_strength_meter');
|
||||
var meterObjLabel = $('#password_strength');
|
||||
var username = $('input[name="username"]');
|
||||
username = username.val();
|
||||
Functions.checkPasswordStrength($(this).val(), meterObj, meterObjLabel, username);
|
||||
});
|
||||
/**
|
||||
* Automatically switching to 'Use Text field' from 'No password' once start writing in text area
|
||||
*/
|
||||
|
||||
$('#text_pma_pw').on('input', function () {
|
||||
if ($('#text_pma_pw').val() !== '') {
|
||||
$('#select_pred_password').val('userdefined');
|
||||
}
|
||||
});
|
||||
$('#text_pma_change_pw').on('keyup', function () {
|
||||
var meterObj = $('#change_password_strength_meter');
|
||||
var meterObjLabel = $('#change_password_strength');
|
||||
Functions.checkPasswordStrength($(this).val(), meterObj, meterObjLabel, CommonParams.get('user'));
|
||||
});
|
||||
/**
|
||||
* Display a notice if sha256_password is selected
|
||||
*/
|
||||
|
||||
$(document).on('change', '#select_authentication_plugin', function () {
|
||||
var selectedPlugin = $(this).val();
|
||||
|
||||
if (selectedPlugin === 'sha256_password') {
|
||||
$('#ssl_reqd_warning').show();
|
||||
} else {
|
||||
$('#ssl_reqd_warning').hide();
|
||||
}
|
||||
});
|
||||
/**
|
||||
* AJAX handler for 'Revoke User'
|
||||
*
|
||||
* @see Functions.ajaxShowMessage()
|
||||
* @memberOf jQuery
|
||||
* @name revoke_user_click
|
||||
*/
|
||||
|
||||
$(document).on('click', '#deleteUserCard .btn.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var $thisButton = $(this);
|
||||
var $form = $('#usersForm');
|
||||
$thisButton.confirm(Messages.strDropUserWarning, $form.attr('action'), function (url) {
|
||||
var $dropUsersDbCheckbox = $('#dropUsersDbCheckbox');
|
||||
|
||||
if ($dropUsersDbCheckbox.is(':checked')) {
|
||||
var isConfirmed = confirm(Messages.strDropDatabaseStrongWarning + '\n' + Functions.sprintf(Messages.strDoYouReally, 'DROP DATABASE'));
|
||||
|
||||
if (!isConfirmed) {
|
||||
// Uncheck the drop users database checkbox
|
||||
$dropUsersDbCheckbox.prop('checked', false);
|
||||
}
|
||||
}
|
||||
|
||||
Functions.ajaxShowMessage(Messages.strRemovingSelectedUsers);
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
$.post(url, $form.serialize() + argsep + 'delete=' + $thisButton.val() + argsep + 'ajax_request=true', function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
Functions.ajaxShowMessage(data.message); // Refresh navigation, if we dropped some databases with the name
|
||||
// that is the same as the username of the deleted user
|
||||
|
||||
if ($('#dropUsersDbCheckbox:checked').length) {
|
||||
Navigation.reload();
|
||||
} // Remove the revoked user from the users list
|
||||
|
||||
|
||||
$form.find('input:checkbox:checked').parents('tr').slideUp('medium', function () {
|
||||
var thisUserInitial = $(this).find('input:checkbox').val().charAt(0).toUpperCase();
|
||||
$(this).remove(); // If this is the last user with thisUserInitial, remove the link from #userAccountsPagination
|
||||
|
||||
if ($('#userRightsTable').find('input:checkbox[value^="' + thisUserInitial + '"], input:checkbox[value^="' + thisUserInitial.toLowerCase() + '"]').length === 0) {
|
||||
$('#userAccountsPagination').find('.page-item > .page-link:contains(' + thisUserInitial + ')').parent('.page-item').addClass('disabled').html('<a class="page-link" href="#" tabindex="-1" aria-disabled="true">' + thisUserInitial + '</a>');
|
||||
} // Re-check the classes of each row
|
||||
|
||||
|
||||
$form.find('tbody').find('tr').each(function (index) {
|
||||
if (index >= 0 && index % 2 === 0) {
|
||||
$(this).removeClass('odd').addClass('even');
|
||||
} else if (index >= 0 && index % 2 !== 0) {
|
||||
$(this).removeClass('even').addClass('odd');
|
||||
}
|
||||
}); // update the checkall checkbox
|
||||
|
||||
$(Functions.checkboxesSel).trigger('change');
|
||||
});
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
});
|
||||
}); // end Revoke User
|
||||
|
||||
const editUserGroupModal = document.getElementById('editUserGroupModal');
|
||||
|
||||
if (editUserGroupModal) {
|
||||
editUserGroupModal.addEventListener('show.bs.modal', EditUserGroup);
|
||||
}
|
||||
/**
|
||||
* AJAX handler for 'Export Privileges'
|
||||
*
|
||||
* @see Functions.ajaxShowMessage()
|
||||
* @memberOf jQuery
|
||||
* @name export_user_click
|
||||
*/
|
||||
|
||||
|
||||
$(document).on('click', 'button.mult_submit[value=export]', function (event) {
|
||||
event.preventDefault(); // can't export if no users checked
|
||||
|
||||
if ($(this.form).find('input:checked').length === 0) {
|
||||
Functions.ajaxShowMessage(Messages.strNoAccountSelected, 2000, 'success');
|
||||
return;
|
||||
}
|
||||
|
||||
var msgbox = Functions.ajaxShowMessage();
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var serverId = CommonParams.get('server');
|
||||
var selectedUsers = $('#usersForm input[name*=\'selected_usr\']:checkbox').serialize();
|
||||
var postStr = selectedUsers + '&submit_mult=export' + argsep + 'ajax_request=true&server=' + serverId;
|
||||
$.post($(this.form).prop('action'), postStr, function (data) {
|
||||
exportPrivilegesModalHandler(data, msgbox);
|
||||
}); // end $.post
|
||||
}); // if exporting non-ajax, highlight anyways
|
||||
|
||||
Functions.getSqlEditor($('textarea.export'));
|
||||
$(document).on('click', 'a.export_user_anchor.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var msgbox = Functions.ajaxShowMessage();
|
||||
$.get($(this).attr('href'), {
|
||||
'ajax_request': true
|
||||
}, function (data) {
|
||||
exportPrivilegesModalHandler(data, msgbox);
|
||||
}); // end $.get
|
||||
}); // end export privileges
|
||||
|
||||
$('button.jsAccountLocking').on('click', AccountLocking.handleEvent);
|
||||
$(document).on('change', 'input[name="ssl_type"]', function () {
|
||||
var $div = $('#specified_div');
|
||||
|
||||
if ($('#ssl_type_SPECIFIED').is(':checked')) {
|
||||
$div.find('input').prop('disabled', false);
|
||||
} else {
|
||||
$div.find('input').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
$(document).on('change', '#checkbox_SSL_priv', function () {
|
||||
var $div = $('#require_ssl_div');
|
||||
|
||||
if ($(this).is(':checked')) {
|
||||
$div.find('input').prop('disabled', false);
|
||||
$('#ssl_type_SPECIFIED').trigger('change');
|
||||
} else {
|
||||
$div.find('input').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
$('#checkbox_SSL_priv').trigger('change');
|
||||
/*
|
||||
* Create submenu for simpler interface
|
||||
*/
|
||||
|
||||
var addOrUpdateSubmenu = function () {
|
||||
var $subNav = $('.nav-pills');
|
||||
var $editUserDialog = $('#edit_user_dialog');
|
||||
var submenuLabel;
|
||||
var submenuLink;
|
||||
var linkNumber; // if submenu exists yet, remove it first
|
||||
|
||||
if ($subNav.length > 0) {
|
||||
$subNav.remove();
|
||||
} // construct a submenu from the existing fieldsets
|
||||
|
||||
|
||||
$subNav = $('<ul></ul>').prop('class', 'nav nav-pills m-2');
|
||||
$('#edit_user_dialog .submenu-item').each(function () {
|
||||
submenuLabel = $(this).find('legend[data-submenu-label]').data('submenu-label');
|
||||
submenuLink = $('<a></a>').prop('class', 'nav-link').prop('href', '#').html(submenuLabel);
|
||||
$('<li></li>').prop('class', 'nav-item').append(submenuLink).appendTo($subNav);
|
||||
}); // click handlers for submenu
|
||||
|
||||
$subNav.find('a').on('click', function (e) {
|
||||
e.preventDefault(); // if already active, ignore click
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subNav.find('a').removeClass('active');
|
||||
$(this).addClass('active'); // which section to show now?
|
||||
|
||||
linkNumber = $subNav.find('a').index($(this)); // hide all sections but the one to show
|
||||
|
||||
$('#edit_user_dialog .submenu-item').hide().eq(linkNumber).show();
|
||||
}); // make first menu item active
|
||||
// TODO: support URL hash history
|
||||
|
||||
$subNav.find('> :first-child a').addClass('active');
|
||||
$editUserDialog.prepend($subNav); // hide all sections but the first
|
||||
|
||||
$('#edit_user_dialog .submenu-item').hide().eq(0).show(); // scroll to the top
|
||||
|
||||
$('html, body').animate({
|
||||
scrollTop: 0
|
||||
}, 'fast');
|
||||
};
|
||||
|
||||
$('input.autofocus').trigger('focus');
|
||||
$(Functions.checkboxesSel).trigger('change');
|
||||
Functions.displayPasswordGenerateButton();
|
||||
|
||||
if ($('#edit_user_dialog').length > 0) {
|
||||
addOrUpdateSubmenu();
|
||||
}
|
||||
/**
|
||||
* Select all privileges
|
||||
*
|
||||
* @param {HTMLElement} e
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
var tableSelectAll = function (e) {
|
||||
const method = e.target.getAttribute('data-select-target');
|
||||
var options = $(method).first().children();
|
||||
options.each(function (_, obj) {
|
||||
obj.selected = true;
|
||||
});
|
||||
};
|
||||
|
||||
$('#select_priv_all').on('click', tableSelectAll);
|
||||
$('#insert_priv_all').on('click', tableSelectAll);
|
||||
$('#update_priv_all').on('click', tableSelectAll);
|
||||
$('#references_priv_all').on('click', tableSelectAll);
|
||||
var windowWidth = $(window).width();
|
||||
$('.jsresponsive').css('max-width', windowWidth - 35 + 'px');
|
||||
$('#addUsersForm').on('submit', function () {
|
||||
return checkAddUser(this);
|
||||
});
|
||||
$('#copyUserForm').on('submit', function () {
|
||||
return checkAddUser(this);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,96 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @package PhpMyAdmin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('server/status/variables.js', function () {
|
||||
$('#filterAlert').off('change');
|
||||
$('#filterText').off('keyup');
|
||||
$('#filterCategory').off('change');
|
||||
$('#dontFormat').off('change');
|
||||
});
|
||||
AJAX.registerOnload('server/status/variables.js', function () {
|
||||
// Filters for status variables
|
||||
var textFilter = null;
|
||||
var alertFilter = $('#filterAlert').prop('checked');
|
||||
var categoryFilter = $('#filterCategory').find(':selected').val();
|
||||
var text = ''; // Holds filter text
|
||||
|
||||
/* 3 Filtering functions */
|
||||
|
||||
$('#filterAlert').on('change', function () {
|
||||
alertFilter = this.checked;
|
||||
filterVariables();
|
||||
});
|
||||
$('#filterCategory').on('change', function () {
|
||||
categoryFilter = $(this).val();
|
||||
filterVariables();
|
||||
});
|
||||
$('#dontFormat').on('change', function () {
|
||||
// Hiding the table while changing values speeds up the process a lot
|
||||
const serverStatusVariables = $('#serverStatusVariables');
|
||||
serverStatusVariables.hide();
|
||||
serverStatusVariables.find('td.value span.original').toggle(this.checked);
|
||||
serverStatusVariables.find('td.value span.formatted').toggle(!this.checked);
|
||||
serverStatusVariables.show();
|
||||
}).trigger('change');
|
||||
$('#filterText').on('keyup', function () {
|
||||
var word = $(this).val().replace(/_/g, ' ');
|
||||
|
||||
if (word.length === 0) {
|
||||
textFilter = null;
|
||||
} else {
|
||||
try {
|
||||
textFilter = new RegExp('(^| )' + word, 'i');
|
||||
$(this).removeClass('error');
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
$(this).addClass('error');
|
||||
textFilter = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text = word;
|
||||
filterVariables();
|
||||
}).trigger('keyup');
|
||||
/* Filters the status variables by name/category/alert in the variables tab */
|
||||
|
||||
function filterVariables() {
|
||||
var usefulLinks = 0;
|
||||
var section = text;
|
||||
|
||||
if (categoryFilter.length > 0) {
|
||||
section = categoryFilter;
|
||||
}
|
||||
|
||||
if (section.length > 1) {
|
||||
$('#linkSuggestions').find('span').each(function () {
|
||||
if ($(this).attr('class').indexOf('status_' + section) !== -1) {
|
||||
usefulLinks++;
|
||||
$(this).css('display', '');
|
||||
} else {
|
||||
$(this).css('display', 'none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (usefulLinks > 0) {
|
||||
$('#linkSuggestions').css('display', '');
|
||||
} else {
|
||||
$('#linkSuggestions').css('display', 'none');
|
||||
}
|
||||
|
||||
$('#serverStatusVariables').find('th.name').each(function () {
|
||||
if ((textFilter === null || textFilter.exec($(this).text())) && (!alertFilter || $(this).next().find('span.text-danger').length > 0) && (categoryFilter.length === 0 || $(this).parent().hasClass('s_' + categoryFilter))) {
|
||||
$(this).parent().css('display', '');
|
||||
} else {
|
||||
$(this).parent().css('display', 'none');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @fileoverview Javascript functions used in server user groups page
|
||||
* @name Server User Groups
|
||||
*
|
||||
* @requires jQuery
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('server/user_groups.js', function () {
|
||||
$('#deleteUserGroupModal').off('show.bs.modal');
|
||||
});
|
||||
/**
|
||||
* Bind event handlers
|
||||
*/
|
||||
|
||||
AJAX.registerOnload('server/user_groups.js', function () {
|
||||
const deleteUserGroupModal = $('#deleteUserGroupModal');
|
||||
deleteUserGroupModal.on('show.bs.modal', function (event) {
|
||||
const userGroupName = $(event.relatedTarget).data('user-group');
|
||||
this.querySelector('.modal-body').innerText = Functions.sprintf(Messages.strDropUserGroupWarning, Functions.escapeHtml(userGroupName));
|
||||
});
|
||||
deleteUserGroupModal.on('shown.bs.modal', function (event) {
|
||||
const userGroupName = $(event.relatedTarget).data('user-group');
|
||||
$('#deleteUserGroupConfirm').on('click', function () {
|
||||
$.post('index.php?route=/server/user-groups', {
|
||||
'deleteUserGroup': true,
|
||||
'userGroup': userGroupName,
|
||||
'ajax_request': true
|
||||
}, AJAX.responseHandler);
|
||||
$('#deleteUserGroupModal').modal('hide');
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,836 @@
|
||||
/**
|
||||
* @fileoverview function used in table data manipulation pages
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @requires js/functions.js
|
||||
*
|
||||
*/
|
||||
|
||||
/* global extendingValidatorMessages */
|
||||
// templates/javascript/variables.twig
|
||||
|
||||
/* global openGISEditor, gisEditorLoaded, loadJSAndGISEditor, loadGISEditor */
|
||||
// js/gis_data_editor.js
|
||||
|
||||
/**
|
||||
* Modify form controls when the "NULL" checkbox is checked
|
||||
*
|
||||
* @param {string} theType the MySQL field type
|
||||
* @param {string} urlField the urlencoded field name - OBSOLETE
|
||||
* @param {string} md5Field the md5 hashed field name
|
||||
* @param {string} multiEdit the multi_edit row sequence number
|
||||
*
|
||||
* @return {boolean} always true
|
||||
*/
|
||||
function nullify(theType, urlField, md5Field, multiEdit) {
|
||||
var rowForm = document.forms.insertForm;
|
||||
|
||||
if (typeof rowForm.elements['funcs' + multiEdit + '[' + md5Field + ']'] !== 'undefined') {
|
||||
rowForm.elements['funcs' + multiEdit + '[' + md5Field + ']'].selectedIndex = -1;
|
||||
} // "ENUM" field with more than 20 characters
|
||||
|
||||
|
||||
if (Number(theType) === 1) {
|
||||
rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'][1].selectedIndex = -1; // Other "ENUM" field
|
||||
} else if (Number(theType) === 2) {
|
||||
var elts = rowForm.elements['fields' + multiEdit + '[' + md5Field + ']']; // when there is just one option in ENUM:
|
||||
|
||||
if (elts.checked) {
|
||||
elts.checked = false;
|
||||
} else {
|
||||
var eltsCnt = elts.length;
|
||||
|
||||
for (var i = 0; i < eltsCnt; i++) {
|
||||
elts[i].checked = false;
|
||||
} // end for
|
||||
|
||||
} // end if
|
||||
// "SET" field
|
||||
|
||||
} else if (Number(theType) === 3) {
|
||||
rowForm.elements['fields' + multiEdit + '[' + md5Field + '][]'].selectedIndex = -1; // Foreign key field (drop-down)
|
||||
} else if (Number(theType) === 4) {
|
||||
rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].selectedIndex = -1; // foreign key field (with browsing icon for foreign values)
|
||||
} else if (Number(theType) === 6) {
|
||||
rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].value = ''; // Other field types
|
||||
} else
|
||||
/* if (theType === 5)*/
|
||||
{
|
||||
rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].value = '';
|
||||
} // end if... else if... else
|
||||
|
||||
|
||||
return true;
|
||||
} // end of the 'nullify()' function
|
||||
|
||||
/**
|
||||
* javascript DateTime format validation.
|
||||
* its used to prevent adding default (0000-00-00 00:00:00) to database when user enter wrong values
|
||||
* Start of validation part
|
||||
*/
|
||||
// function checks the number of days in febuary
|
||||
|
||||
|
||||
function daysInFebruary(year) {
|
||||
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
|
||||
} // function to convert single digit to double digit
|
||||
|
||||
|
||||
function fractionReplace(number) {
|
||||
var num = parseInt(number, 10);
|
||||
return num >= 1 && num <= 9 ? '0' + num : '00';
|
||||
}
|
||||
/* function to check the validity of date
|
||||
* The following patterns are accepted in this validation (accepted in mysql as well)
|
||||
* 1) 2001-12-23
|
||||
* 2) 2001-1-2
|
||||
* 3) 02-12-23
|
||||
* 4) And instead of using '-' the following punctuations can be used (+,.,*,^,@,/) All these are accepted by mysql as well. Therefore no issues
|
||||
*/
|
||||
|
||||
|
||||
function isDate(val, tmstmp) {
|
||||
var value = val.replace(/[.|*|^|+|//|@]/g, '-');
|
||||
var arrayVal = value.split('-');
|
||||
|
||||
for (var a = 0; a < arrayVal.length; a++) {
|
||||
if (arrayVal[a].length === 1) {
|
||||
arrayVal[a] = fractionReplace(arrayVal[a]);
|
||||
}
|
||||
}
|
||||
|
||||
value = arrayVal.join('-');
|
||||
var pos = 2;
|
||||
var dtexp = new RegExp(/^([0-9]{4})-(((01|03|05|07|08|10|12)-((0[0-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)-((0[0-9])|([1-2][0-9])|30))|((00)-(00)))$/);
|
||||
|
||||
if (value.length === 8) {
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (dtexp.test(value)) {
|
||||
var month = parseInt(value.substring(pos + 3, pos + 5), 10);
|
||||
var day = parseInt(value.substring(pos + 6, pos + 8), 10);
|
||||
var year = parseInt(value.substring(0, pos + 2), 10);
|
||||
|
||||
if (month === 2 && day > daysInFebruary(year)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.substring(0, pos + 2).length === 2) {
|
||||
year = parseInt('20' + value.substring(0, pos + 2), 10);
|
||||
}
|
||||
|
||||
if (tmstmp === true) {
|
||||
if (year < 1978) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (year > 2038 || year > 2037 && day > 19 && month >= 1 || year > 2037 && month > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/* function to check the validity of time
|
||||
* The following patterns are accepted in this validation (accepted in mysql as well)
|
||||
* 1) 2:3:4
|
||||
* 2) 2:23:43
|
||||
* 3) 2:23:43.123456
|
||||
*/
|
||||
|
||||
|
||||
function isTime(val) {
|
||||
var arrayVal = val.split(':');
|
||||
|
||||
for (var a = 0, l = arrayVal.length; a < l; a++) {
|
||||
if (arrayVal[a].length === 1) {
|
||||
arrayVal[a] = fractionReplace(arrayVal[a]);
|
||||
}
|
||||
}
|
||||
|
||||
var newVal = arrayVal.join(':');
|
||||
var tmexp = new RegExp(/^(-)?(([0-7]?[0-9][0-9])|(8[0-2][0-9])|(83[0-8])):((0[0-9])|([1-5][0-9])):((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$/);
|
||||
return tmexp.test(newVal);
|
||||
}
|
||||
/**
|
||||
* To check whether insert section is ignored or not
|
||||
* @param {string} multiEdit
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
|
||||
function checkForCheckbox(multiEdit) {
|
||||
if ($('#insert_ignore_' + multiEdit).length) {
|
||||
return $('#insert_ignore_' + multiEdit).is(':unchecked');
|
||||
}
|
||||
|
||||
return true;
|
||||
} // used in Search page mostly for INT fields
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
|
||||
function verifyAfterSearchFieldChange(index, searchFormId) {
|
||||
var $thisInput = $('input[name=\'criteriaValues[' + index + ']\']'); // Add data-skip-validators attribute to skip validation in changeValueFieldType function
|
||||
|
||||
if ($('#fieldID_' + index).data('data-skip-validators')) {
|
||||
$(searchFormId).validate().destroy();
|
||||
return;
|
||||
} // validation for integer type
|
||||
|
||||
|
||||
if ($thisInput.data('type') === 'INT' || $thisInput.data('type') === 'TINYINT') {
|
||||
// Trim spaces if it's an integer
|
||||
$thisInput.val($thisInput.val().trim());
|
||||
var hasMultiple = $thisInput.prop('multiple');
|
||||
|
||||
if (hasMultiple) {
|
||||
$(searchFormId).validate({
|
||||
// update errors as we write
|
||||
onkeyup: function (element) {
|
||||
$(element).valid();
|
||||
}
|
||||
}); // validator method for IN(...), NOT IN(...)
|
||||
// BETWEEN and NOT BETWEEN
|
||||
|
||||
jQuery.validator.addMethod('validationFunctionForMultipleInt', function (value) {
|
||||
return value.match(/^(?:(?:\d\s*)|\s*)+(?:,\s*\d+)*$/i) !== null;
|
||||
}, Messages.strEnterValidNumber);
|
||||
validateMultipleIntField($thisInput, true);
|
||||
} else {
|
||||
$(searchFormId).validate({
|
||||
// update errors as we write
|
||||
onkeyup: function (element) {
|
||||
$(element).valid();
|
||||
}
|
||||
});
|
||||
validateIntField($thisInput, true);
|
||||
} // Update error on dropdown change
|
||||
|
||||
|
||||
$thisInput.valid();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Validate the an input contains multiple int values
|
||||
* @param {jQuery} jqueryInput the Jquery object
|
||||
* @param {boolean} returnValueIfFine the value to return if the validator passes
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
function validateMultipleIntField(jqueryInput, returnValueIfFine) {
|
||||
// removing previous rules
|
||||
jqueryInput.rules('remove');
|
||||
jqueryInput.rules('add', {
|
||||
validationFunctionForMultipleInt: {
|
||||
param: jqueryInput.value,
|
||||
depends: function () {
|
||||
return returnValueIfFine;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Validate the an input contains an int value
|
||||
* @param {jQuery} jqueryInput the Jquery object
|
||||
* @param {boolean} returnValueIfIsNumber the value to return if the validator passes
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
|
||||
function validateIntField(jqueryInput, returnValueIfIsNumber) {
|
||||
var mini = parseInt(jqueryInput.data('min'));
|
||||
var maxi = parseInt(jqueryInput.data('max')); // removing previous rules
|
||||
|
||||
jqueryInput.rules('remove');
|
||||
jqueryInput.rules('add', {
|
||||
number: {
|
||||
param: true,
|
||||
depends: function () {
|
||||
return returnValueIfIsNumber;
|
||||
}
|
||||
},
|
||||
min: {
|
||||
param: mini,
|
||||
depends: function () {
|
||||
if (isNaN(jqueryInput.val())) {
|
||||
return false;
|
||||
} else {
|
||||
return returnValueIfIsNumber;
|
||||
}
|
||||
}
|
||||
},
|
||||
max: {
|
||||
param: maxi,
|
||||
depends: function () {
|
||||
if (isNaN(jqueryInput.val())) {
|
||||
return false;
|
||||
} else {
|
||||
return returnValueIfIsNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function verificationsAfterFieldChange(urlField, multiEdit, theType) {
|
||||
var evt = window.event || arguments.callee.caller.arguments[0];
|
||||
var target = evt.target || evt.srcElement;
|
||||
var $thisInput = $(':input[name^=\'fields[multi_edit][' + multiEdit + '][' + urlField + ']\']'); // the function drop-down that corresponds to this input field
|
||||
|
||||
var $thisFunction = $('select[name=\'funcs[multi_edit][' + multiEdit + '][' + urlField + ']\']');
|
||||
var functionSelected = false;
|
||||
|
||||
if (typeof $thisFunction.val() !== 'undefined' && $thisFunction.val() !== null && $thisFunction.val().length > 0) {
|
||||
functionSelected = true;
|
||||
} // To generate the textbox that can take the salt
|
||||
|
||||
|
||||
var newSaltBox = '<br><input type=text name=salt[multi_edit][' + multiEdit + '][' + urlField + ']' + ' id=salt_' + target.id + ' placeholder=\'' + Messages.strEncryptionKey + '\'>'; // If encrypting or decrypting functions that take salt as input is selected append the new textbox for salt
|
||||
|
||||
if (target.value === 'AES_ENCRYPT' || target.value === 'AES_DECRYPT' || target.value === 'DES_ENCRYPT' || target.value === 'DES_DECRYPT' || target.value === 'ENCRYPT') {
|
||||
if (!$('#salt_' + target.id).length) {
|
||||
$thisInput.after(newSaltBox);
|
||||
}
|
||||
} else {
|
||||
// Remove the textbox for salt
|
||||
$('#salt_' + target.id).prev('br').remove();
|
||||
$('#salt_' + target.id).remove();
|
||||
} // Remove possible blocking rules if the user changed functions
|
||||
|
||||
|
||||
$('#' + target.id).rules('remove', 'validationFunctionForMd5');
|
||||
$('#' + target.id).rules('remove', 'validationFunctionForAesDesEncrypt');
|
||||
|
||||
if (target.value === 'MD5') {
|
||||
$('#' + target.id).rules('add', {
|
||||
validationFunctionForMd5: {
|
||||
param: $thisInput,
|
||||
depends: function () {
|
||||
return checkForCheckbox(multiEdit);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (target.value === 'DES_ENCRYPT' || target.value === 'AES_ENCRYPT') {
|
||||
$('#' + target.id).rules('add', {
|
||||
validationFunctionForAesDesEncrypt: {
|
||||
param: $thisInput,
|
||||
depends: function () {
|
||||
return checkForCheckbox(multiEdit);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (target.value === 'HEX' && theType.substring(0, 3) === 'int') {
|
||||
// Add note when HEX function is selected on a int
|
||||
var newHexInfo = '<br><p id="note' + target.id + '">' + Messages.HexConversionInfo + '</p>';
|
||||
|
||||
if (!$('#note' + target.id).length) {
|
||||
$thisInput.after(newHexInfo);
|
||||
}
|
||||
} else {
|
||||
$('#note' + target.id).prev('br').remove();
|
||||
$('#note' + target.id).remove();
|
||||
} // Unchecks the corresponding "NULL" control
|
||||
|
||||
|
||||
$('input[name=\'fields_null[multi_edit][' + multiEdit + '][' + urlField + ']\']').prop('checked', false); // Unchecks the Ignore checkbox for the current row
|
||||
|
||||
$('input[name=\'insert_ignore_' + multiEdit + '\']').prop('checked', false);
|
||||
var charExceptionHandling;
|
||||
|
||||
if (theType.substring(0, 4) === 'char') {
|
||||
charExceptionHandling = theType.substring(5, 6);
|
||||
} else if (theType.substring(0, 7) === 'varchar') {
|
||||
charExceptionHandling = theType.substring(8, 9);
|
||||
}
|
||||
|
||||
if (functionSelected) {
|
||||
$thisInput.removeAttr('min');
|
||||
$thisInput.removeAttr('max'); // @todo: put back attributes if corresponding function is deselected
|
||||
}
|
||||
|
||||
if ($thisInput.data('rulesadded') === null && !functionSelected) {
|
||||
// call validate before adding rules
|
||||
$($thisInput[0].form).validate(); // validate for date time
|
||||
|
||||
if (theType === 'datetime' || theType === 'time' || theType === 'date' || theType === 'timestamp') {
|
||||
$thisInput.rules('add', {
|
||||
validationFunctionForDateTime: {
|
||||
param: theType,
|
||||
depends: function () {
|
||||
return checkForCheckbox(multiEdit);
|
||||
}
|
||||
}
|
||||
});
|
||||
} // validation for integer type
|
||||
|
||||
|
||||
if ($thisInput.data('type') === 'INT') {
|
||||
validateIntField($thisInput, checkForCheckbox(multiEdit)); // validation for CHAR types
|
||||
} else if ($thisInput.data('type') === 'CHAR') {
|
||||
var maxlen = $thisInput.data('maxlength');
|
||||
|
||||
if (typeof maxlen !== 'undefined') {
|
||||
if (maxlen <= 4) {
|
||||
maxlen = charExceptionHandling;
|
||||
}
|
||||
|
||||
$thisInput.rules('add', {
|
||||
maxlength: {
|
||||
param: maxlen,
|
||||
depends: function () {
|
||||
return checkForCheckbox(multiEdit);
|
||||
}
|
||||
}
|
||||
});
|
||||
} // validate binary & blob types
|
||||
|
||||
} else if ($thisInput.data('type') === 'HEX') {
|
||||
$thisInput.rules('add', {
|
||||
validationFunctionForHex: {
|
||||
param: true,
|
||||
depends: function () {
|
||||
return checkForCheckbox(multiEdit);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$thisInput.data('rulesadded', true);
|
||||
} else if ($thisInput.data('rulesadded') === true && functionSelected) {
|
||||
// remove any rules added
|
||||
$thisInput.rules('remove'); // remove any error messages
|
||||
|
||||
$thisInput.removeClass('error').removeAttr('aria-invalid').siblings('.error').remove();
|
||||
$thisInput.data('rulesadded', null);
|
||||
}
|
||||
}
|
||||
/* End of fields validation*/
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
|
||||
|
||||
AJAX.registerTeardown('table/change.js', function () {
|
||||
$(document).off('click', 'span.open_gis_editor');
|
||||
$(document).off('click', 'input[name^=\'insert_ignore_\']');
|
||||
$(document).off('click', 'input[name=\'gis_data[save]\']');
|
||||
$(document).off('click', 'input.checkbox_null');
|
||||
$('select[name="submit_type"]').off('change');
|
||||
$(document).off('change', '#insert_rows');
|
||||
});
|
||||
/**
|
||||
* Ajax handlers for Change Table page
|
||||
*
|
||||
* Actions Ajaxified here:
|
||||
* Submit Data to be inserted into the table.
|
||||
* Restart insertion with 'N' rows.
|
||||
*/
|
||||
|
||||
AJAX.registerOnload('table/change.js', function () {
|
||||
if ($('#insertForm').length) {
|
||||
// validate the comment form when it is submitted
|
||||
$('#insertForm').validate();
|
||||
jQuery.validator.addMethod('validationFunctionForHex', function (value) {
|
||||
return value.match(/^[a-f0-9]*$/i) !== null;
|
||||
});
|
||||
jQuery.validator.addMethod('validationFunctionForMd5', function (value, element, options) {
|
||||
return !(value.substring(0, 3) === 'MD5' && typeof options.data('maxlength') !== 'undefined' && options.data('maxlength') < 32);
|
||||
});
|
||||
jQuery.validator.addMethod('validationFunctionForAesDesEncrypt', function (value, element, options) {
|
||||
var funType = value.substring(0, 3);
|
||||
|
||||
if (funType !== 'AES' && funType !== 'DES') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var dataType = options.data('type');
|
||||
|
||||
if (dataType === 'HEX' || dataType === 'CHAR') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
jQuery.validator.addMethod('validationFunctionForDateTime', function (value, element, options) {
|
||||
var dtValue = value;
|
||||
var theType = options;
|
||||
|
||||
if (theType === 'date') {
|
||||
return isDate(dtValue);
|
||||
} else if (theType === 'time') {
|
||||
return isTime(dtValue);
|
||||
} else if (theType === 'datetime' || theType === 'timestamp') {
|
||||
var tmstmp = false;
|
||||
dtValue = dtValue.trim();
|
||||
|
||||
if (dtValue === 'CURRENT_TIMESTAMP' || dtValue === 'current_timestamp()') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (theType === 'timestamp') {
|
||||
tmstmp = true;
|
||||
}
|
||||
|
||||
if (dtValue === '0000-00-00 00:00:00') {
|
||||
return true;
|
||||
}
|
||||
|
||||
var dv = dtValue.indexOf(' ');
|
||||
|
||||
if (dv === -1) {
|
||||
// Only the date component, which is valid
|
||||
return isDate(dtValue, tmstmp);
|
||||
}
|
||||
|
||||
return isDate(dtValue.substring(0, dv), tmstmp) && isTime(dtValue.substring(dv + 1));
|
||||
}
|
||||
});
|
||||
}
|
||||
/*
|
||||
* message extending script must be run
|
||||
* after initiation of functions
|
||||
*/
|
||||
|
||||
|
||||
extendingValidatorMessages();
|
||||
$.datepicker.initialized = false;
|
||||
$(document).on('click', 'span.open_gis_editor', function (event) {
|
||||
event.preventDefault();
|
||||
var $span = $(this); // Current value
|
||||
|
||||
var value = $span.parent('td').children('input[type=\'text\']').val(); // Field name
|
||||
|
||||
var field = $span.parents('tr').children('td').first().find('input[type=\'hidden\']').val(); // Column type
|
||||
|
||||
var type = $span.parents('tr').find('span.column_type').text(); // Names of input field and null checkbox
|
||||
|
||||
var inputName = $span.parent('td').children('input[type=\'text\']').attr('name');
|
||||
openGISEditor();
|
||||
|
||||
if (!gisEditorLoaded) {
|
||||
loadJSAndGISEditor(value, field, type, inputName);
|
||||
} else {
|
||||
loadGISEditor(value, field, type, inputName);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Forced validation check of fields
|
||||
*/
|
||||
|
||||
$(document).on('click', 'input[name^=\'insert_ignore_\']', function () {
|
||||
$('#insertForm').valid();
|
||||
});
|
||||
/**
|
||||
* Uncheck the null checkbox as geometry data is placed on the input field
|
||||
*/
|
||||
|
||||
$(document).on('click', 'input[name=\'gis_data[save]\']', function () {
|
||||
var inputName = $('form#gis_data_editor_form').find('input[name=\'input_name\']').val();
|
||||
var currentRow = $('input[name=\'' + inputName + '\']').parents('tr');
|
||||
var $nullCheckbox = currentRow.find('.checkbox_null');
|
||||
$nullCheckbox.prop('checked', false);
|
||||
var rowId = currentRow.find('.open_gis_editor').data('row-id'); // Unchecks the Ignore checkbox for the current row
|
||||
|
||||
$('input[name=\'insert_ignore_' + rowId + '\']').prop('checked', false);
|
||||
});
|
||||
/**
|
||||
* Handles all current checkboxes for Null; this only takes care of the
|
||||
* checkboxes on currently displayed rows as the rows generated by
|
||||
* "Continue insertion" are handled in the "Continue insertion" code
|
||||
*
|
||||
*/
|
||||
|
||||
$(document).on('click', 'input.checkbox_null', function () {
|
||||
nullify( // use hidden fields populated by /table/change
|
||||
$(this).siblings('.nullify_code').val(), $(this).closest('tr').find('input:hidden').first().val(), $(this).siblings('.hashed_field').val(), $(this).siblings('.multi_edit').val());
|
||||
});
|
||||
/**
|
||||
* Reset the auto_increment column to 0 when selecting any of the
|
||||
* insert options in submit_type-dropdown. Only perform the reset
|
||||
* when we are in edit-mode, and not in insert-mode(no previous value
|
||||
* available).
|
||||
*/
|
||||
|
||||
$('select[name="submit_type"]').on('change', function () {
|
||||
var thisElemSubmitTypeVal = $(this).val();
|
||||
var $table = $('table.insertRowTable');
|
||||
var autoIncrementColumn = $table.find('input[name^="auto_increment"]');
|
||||
autoIncrementColumn.each(function () {
|
||||
var $thisElemAIField = $(this);
|
||||
var thisElemName = $thisElemAIField.attr('name');
|
||||
var prevValueField = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields_prev') + '"]');
|
||||
var valueField = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields') + '"]');
|
||||
var previousValue = $(prevValueField).val();
|
||||
|
||||
if (previousValue !== undefined) {
|
||||
if (thisElemSubmitTypeVal === 'insert' || thisElemSubmitTypeVal === 'insertignore' || thisElemSubmitTypeVal === 'showinsert') {
|
||||
$(valueField).val(null);
|
||||
} else {
|
||||
$(valueField).val(previousValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Handle ENTER key when press on Continue insert with field
|
||||
*/
|
||||
|
||||
$('#insert_rows').on('keypress', function (e) {
|
||||
var key = e.which;
|
||||
|
||||
if (key === 13) {
|
||||
addNewContinueInsertionFields(e);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Continue Insertion form
|
||||
*/
|
||||
|
||||
$(document).on('change', '#insert_rows', addNewContinueInsertionFields);
|
||||
});
|
||||
|
||||
function addNewContinueInsertionFields(event) {
|
||||
event.preventDefault();
|
||||
/**
|
||||
* @var columnCount Number of number of columns table has.
|
||||
*/
|
||||
|
||||
var columnCount = $('table.insertRowTable').first().find('tr').has('input[name*=\'fields_name\']').length;
|
||||
/**
|
||||
* @var curr_rows Number of current insert rows already on page
|
||||
*/
|
||||
|
||||
var currRows = $('table.insertRowTable').length;
|
||||
/**
|
||||
* @var target_rows Number of rows the user wants
|
||||
*/
|
||||
|
||||
var targetRows = $('#insert_rows').val(); // remove all datepickers
|
||||
|
||||
$('input.datefield, input.datetimefield').each(function () {
|
||||
$(this).datepicker('destroy');
|
||||
});
|
||||
|
||||
if (currRows < targetRows) {
|
||||
var tempIncrementIndex = function () {
|
||||
var $thisElement = $(this);
|
||||
/**
|
||||
* Extract the index from the name attribute for all input/select fields and increment it
|
||||
* name is of format funcs[multi_edit][10][<long random string of alphanum chars>]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var this_name String containing name of the input/select elements
|
||||
*/
|
||||
|
||||
var thisName = $thisElement.attr('name');
|
||||
/** split {@link thisName} at [10], so we have the parts that can be concatenated later */
|
||||
|
||||
var nameParts = thisName.split(/\[\d+\]/);
|
||||
/** extract the [10] from {@link nameParts} */
|
||||
|
||||
var oldRowIndexString = thisName.match(/\[\d+\]/)[0];
|
||||
/** extract 10 - had to split into two steps to accomodate double digits */
|
||||
|
||||
var oldRowIndex = parseInt(oldRowIndexString.match(/\d+/)[0], 10);
|
||||
/** calculate next index i.e. 11 */
|
||||
|
||||
newRowIndex = oldRowIndex + 1;
|
||||
/** generate the new name i.e. funcs[multi_edit][11][foobarbaz] */
|
||||
|
||||
var newName = nameParts[0] + '[' + newRowIndex + ']' + nameParts[1];
|
||||
var hashedField = nameParts[1].match(/\[(.+)\]/)[1];
|
||||
$thisElement.attr('name', newName);
|
||||
/** If element is select[name*='funcs'], update id */
|
||||
|
||||
if ($thisElement.is('select[name*=\'funcs\']')) {
|
||||
var thisId = $thisElement.attr('id');
|
||||
var idParts = thisId.split(/_/);
|
||||
var oldIdIndex = idParts[1];
|
||||
var prevSelectedValue = $('#field_' + oldIdIndex + '_1').val();
|
||||
var newIdIndex = parseInt(oldIdIndex) + columnCount;
|
||||
var newId = 'field_' + newIdIndex + '_1';
|
||||
$thisElement.attr('id', newId);
|
||||
$thisElement.find('option').filter(function () {
|
||||
return $(this).text() === prevSelectedValue;
|
||||
}).attr('selected', 'selected'); // If salt field is there then update its id.
|
||||
|
||||
var nextSaltInput = $thisElement.parent().next('td').next('td').find('input[name*=\'salt\']');
|
||||
|
||||
if (nextSaltInput.length !== 0) {
|
||||
nextSaltInput.attr('id', 'salt_' + newId);
|
||||
}
|
||||
} // handle input text fields and textareas
|
||||
|
||||
|
||||
if ($thisElement.is('.textfield') || $thisElement.is('.char') || $thisElement.is('textarea')) {
|
||||
// do not remove the 'value' attribute for ENUM columns
|
||||
// special handling for radio fields after updating ids to unique - see below
|
||||
if ($thisElement.closest('tr').find('span.column_type').html() !== 'enum') {
|
||||
$thisElement.val($thisElement.closest('tr').find('span.default_value').html());
|
||||
}
|
||||
|
||||
$thisElement.off('change') // Remove onchange attribute that was placed
|
||||
// by /table/change; it refers to the wrong row index
|
||||
.attr('onchange', null) // Keep these values to be used when the element
|
||||
// will change
|
||||
.data('hashed_field', hashedField).data('new_row_index', newRowIndex).on('change', function () {
|
||||
var $changedElement = $(this);
|
||||
verificationsAfterFieldChange($changedElement.data('hashed_field'), $changedElement.data('new_row_index'), $changedElement.closest('tr').find('span.column_type').html());
|
||||
});
|
||||
}
|
||||
|
||||
if ($thisElement.is('.checkbox_null')) {
|
||||
$thisElement // this event was bound earlier by jQuery but
|
||||
// to the original row, not the cloned one, so unbind()
|
||||
.off('click') // Keep these values to be used when the element
|
||||
// will be clicked
|
||||
.data('hashed_field', hashedField).data('new_row_index', newRowIndex).on('click', function () {
|
||||
var $changedElement = $(this);
|
||||
nullify($changedElement.siblings('.nullify_code').val(), $thisElement.closest('tr').find('input:hidden').first().val(), $changedElement.data('hashed_field'), '[multi_edit][' + $changedElement.data('new_row_index') + ']');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var tempReplaceAnchor = function () {
|
||||
var $anchor = $(this);
|
||||
var newValue = 'rownumber=' + newRowIndex; // needs improvement in case something else inside
|
||||
// the href contains this pattern
|
||||
|
||||
var newHref = $anchor.attr('href').replace(/rownumber=\d+/, newValue);
|
||||
$anchor.attr('href', newHref);
|
||||
};
|
||||
|
||||
var restoreValue = function () {
|
||||
if ($(this).closest('tr').find('span.column_type').html() === 'enum') {
|
||||
if ($(this).val() === $checkedValue) {
|
||||
$(this).prop('checked', true);
|
||||
} else {
|
||||
$(this).prop('checked', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (currRows < targetRows) {
|
||||
/**
|
||||
* @var $last_row Object referring to the last row
|
||||
*/
|
||||
var $lastRow = $('#insertForm').find('.insertRowTable').last(); // need to access this at more than one level
|
||||
// (also needs improvement because it should be calculated
|
||||
// just once per cloned row, not once per column)
|
||||
|
||||
var newRowIndex = 0;
|
||||
var $checkedValue = $lastRow.find('input:checked').val(); // Clone the insert tables
|
||||
|
||||
$lastRow.clone(true, true).insertBefore('#actions_panel').find('input[name*=multi_edit],select[name*=multi_edit],textarea[name*=multi_edit]').each(tempIncrementIndex).end().find('.foreign_values_anchor').each(tempReplaceAnchor);
|
||||
var $oldRow = $lastRow.find('.textfield');
|
||||
$oldRow.each(restoreValue); // set the value of enum field of new row to default
|
||||
|
||||
var $newRow = $('#insertForm').find('.insertRowTable').last();
|
||||
$newRow.find('.textfield').each(function () {
|
||||
if ($(this).closest('tr').find('span.column_type').html() === 'enum') {
|
||||
if ($(this).val() === $(this).closest('tr').find('span.default_value').html()) {
|
||||
$(this).prop('checked', true);
|
||||
} else {
|
||||
$(this).prop('checked', false);
|
||||
}
|
||||
}
|
||||
}); // Insert/Clone the ignore checkboxes
|
||||
|
||||
if (currRows === 1) {
|
||||
$('<input id="insert_ignore_1" type="checkbox" name="insert_ignore_1" checked="checked">').insertBefore($('table.insertRowTable').last()).after('<label for="insert_ignore_1">' + Messages.strIgnore + '</label>');
|
||||
} else {
|
||||
/**
|
||||
* @var $last_checkbox Object reference to the last checkbox in #insertForm
|
||||
*/
|
||||
var $lastCheckbox = $('#insertForm').children('input:checkbox').last();
|
||||
/** name of {@link $lastCheckbox} */
|
||||
|
||||
var lastCheckboxName = $lastCheckbox.attr('name');
|
||||
/** index of {@link $lastCheckbox} */
|
||||
|
||||
var lastCheckboxIndex = parseInt(lastCheckboxName.match(/\d+/), 10);
|
||||
/** name of new {@link $lastCheckbox} */
|
||||
|
||||
var newName = lastCheckboxName.replace(/\d+/, lastCheckboxIndex + 1);
|
||||
$('<br><div class="clearfloat"></div>').insertBefore($('table.insertRowTable').last());
|
||||
$lastCheckbox.clone().attr({
|
||||
'id': newName,
|
||||
'name': newName
|
||||
}).prop('checked', true).insertBefore($('table.insertRowTable').last());
|
||||
$('label[for^=insert_ignore]').last().clone().attr('for', newName).insertBefore($('table.insertRowTable').last());
|
||||
$('<br>').insertBefore($('table.insertRowTable').last());
|
||||
}
|
||||
|
||||
currRows++;
|
||||
} // recompute tabindex for text fields and other controls at footer;
|
||||
// IMO it's not really important to handle the tabindex for
|
||||
// function and Null
|
||||
|
||||
|
||||
var tabIndex = 0;
|
||||
$('.textfield, .char, textarea').each(function () {
|
||||
tabIndex++;
|
||||
$(this).attr('tabindex', tabIndex); // update the IDs of textfields to ensure that they are unique
|
||||
|
||||
$(this).attr('id', 'field_' + tabIndex + '_3');
|
||||
});
|
||||
$('.control_at_footer').each(function () {
|
||||
tabIndex++;
|
||||
$(this).attr('tabindex', tabIndex);
|
||||
});
|
||||
} else if (currRows > targetRows) {
|
||||
/**
|
||||
* Displays alert if data loss possible on decrease
|
||||
* of rows.
|
||||
*/
|
||||
var checkLock = jQuery.isEmptyObject(AJAX.lockedTargets);
|
||||
|
||||
if (checkLock || confirm(Messages.strConfirmRowChange) === true) {
|
||||
while (currRows > targetRows) {
|
||||
$('input[id^=insert_ignore]').last().nextUntil('fieldset').addBack().remove();
|
||||
currRows--;
|
||||
}
|
||||
} else {
|
||||
document.getElementById('insert_rows').value = currRows;
|
||||
}
|
||||
} // Add all the required datepickers back
|
||||
|
||||
|
||||
Functions.addDateTimePicker();
|
||||
} // eslint-disable-next-line no-unused-vars
|
||||
|
||||
|
||||
function changeValueFieldType(elem, searchIndex) {
|
||||
var fieldsValue = $('input#fieldID_' + searchIndex);
|
||||
|
||||
if (0 === fieldsValue.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var type = $(elem).val();
|
||||
|
||||
if ('LIKE' === type || 'LIKE %...%' === type || 'NOT LIKE' === type || 'NOT LIKE %...%' === type) {
|
||||
$('#fieldID_' + searchIndex).data('data-skip-validators', true);
|
||||
return;
|
||||
} else {
|
||||
$('#fieldID_' + searchIndex).data('data-skip-validators', false);
|
||||
}
|
||||
|
||||
if ('IN (...)' === type || 'NOT IN (...)' === type || 'BETWEEN' === type || 'NOT BETWEEN' === type) {
|
||||
$('#fieldID_' + searchIndex).prop('multiple', true);
|
||||
} else {
|
||||
$('#fieldID_' + searchIndex).prop('multiple', false);
|
||||
}
|
||||
}
|
@ -0,0 +1,435 @@
|
||||
/* global ColumnType, DataTable, JQPlotChartFactory */
|
||||
// js/chart.js
|
||||
|
||||
/* global codeMirrorEditor */
|
||||
// js/functions.js
|
||||
var chartData = {};
|
||||
var tempChartTitle;
|
||||
var currentChart = null;
|
||||
var currentSettings = null;
|
||||
var dateTimeCols = [];
|
||||
var numericCols = [];
|
||||
|
||||
function extractDate(dateString) {
|
||||
var matches;
|
||||
var match;
|
||||
var dateTimeRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/;
|
||||
var dateRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2}/;
|
||||
matches = dateTimeRegExp.exec(dateString);
|
||||
|
||||
if (matches !== null && matches.length > 0) {
|
||||
match = matches[0];
|
||||
return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2), match.substr(11, 2), match.substr(14, 2), match.substr(17, 2));
|
||||
} else {
|
||||
matches = dateRegExp.exec(dateString);
|
||||
|
||||
if (matches !== null && matches.length > 0) {
|
||||
match = matches[0];
|
||||
return new Date(match.substr(0, 4), parseInt(match.substr(5, 2), 10) - 1, match.substr(8, 2));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function queryChart(data, columnNames, settings) {
|
||||
if ($('#querychart').length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var plotSettings = {
|
||||
title: {
|
||||
text: settings.title,
|
||||
escapeHtml: true
|
||||
},
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
shadow: false,
|
||||
background: 'rgba(0,0,0,0)'
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
placement: 'outsideGrid',
|
||||
location: 'e',
|
||||
rendererOptions: {
|
||||
numberColumns: 2
|
||||
}
|
||||
},
|
||||
axes: {
|
||||
xaxis: {
|
||||
label: Functions.escapeHtml(settings.xaxisLabel)
|
||||
},
|
||||
yaxis: {
|
||||
label: settings.yaxisLabel
|
||||
}
|
||||
},
|
||||
stackSeries: settings.stackSeries
|
||||
}; // create the chart
|
||||
|
||||
var factory = new JQPlotChartFactory();
|
||||
var chart = factory.createChart(settings.type, 'querychart'); // create the data table and add columns
|
||||
|
||||
var dataTable = new DataTable();
|
||||
|
||||
if (settings.type === 'timeline') {
|
||||
dataTable.addColumn(ColumnType.DATE, columnNames[settings.mainAxis]);
|
||||
} else if (settings.type === 'scatter') {
|
||||
dataTable.addColumn(ColumnType.NUMBER, columnNames[settings.mainAxis]);
|
||||
} else {
|
||||
dataTable.addColumn(ColumnType.STRING, columnNames[settings.mainAxis]);
|
||||
}
|
||||
|
||||
var i;
|
||||
var values = [];
|
||||
|
||||
if (settings.seriesColumn === null) {
|
||||
$.each(settings.selectedSeries, function (index, element) {
|
||||
dataTable.addColumn(ColumnType.NUMBER, columnNames[element]);
|
||||
}); // set data to the data table
|
||||
|
||||
var columnsToExtract = [settings.mainAxis];
|
||||
$.each(settings.selectedSeries, function (index, element) {
|
||||
columnsToExtract.push(element);
|
||||
});
|
||||
var newRow;
|
||||
var row;
|
||||
var col;
|
||||
|
||||
for (i = 0; i < data.length; i++) {
|
||||
row = data[i];
|
||||
newRow = [];
|
||||
|
||||
for (var j = 0; j < columnsToExtract.length; j++) {
|
||||
col = columnNames[columnsToExtract[j]];
|
||||
|
||||
if (j === 0) {
|
||||
if (settings.type === 'timeline') {
|
||||
// first column is date type
|
||||
newRow.push(extractDate(row[col]));
|
||||
} else if (settings.type === 'scatter') {
|
||||
newRow.push(parseFloat(row[col]));
|
||||
} else {
|
||||
// first column is string type
|
||||
newRow.push(row[col]);
|
||||
}
|
||||
} else {
|
||||
// subsequent columns are of type, number
|
||||
newRow.push(parseFloat(row[col]));
|
||||
}
|
||||
}
|
||||
|
||||
values.push(newRow);
|
||||
}
|
||||
|
||||
dataTable.setData(values);
|
||||
} else {
|
||||
var seriesNames = {};
|
||||
var seriesNumber = 1;
|
||||
var seriesColumnName = columnNames[settings.seriesColumn];
|
||||
|
||||
for (i = 0; i < data.length; i++) {
|
||||
if (!seriesNames[data[i][seriesColumnName]]) {
|
||||
seriesNames[data[i][seriesColumnName]] = seriesNumber;
|
||||
seriesNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
$.each(seriesNames, function (seriesName) {
|
||||
dataTable.addColumn(ColumnType.NUMBER, seriesName);
|
||||
});
|
||||
var valueMap = {};
|
||||
var xValue;
|
||||
var value;
|
||||
var mainAxisName = columnNames[settings.mainAxis];
|
||||
var valueColumnName = columnNames[settings.valueColumn];
|
||||
|
||||
for (i = 0; i < data.length; i++) {
|
||||
xValue = data[i][mainAxisName];
|
||||
value = valueMap[xValue];
|
||||
|
||||
if (!value) {
|
||||
value = [xValue];
|
||||
valueMap[xValue] = value;
|
||||
}
|
||||
|
||||
seriesNumber = seriesNames[data[i][seriesColumnName]];
|
||||
value[seriesNumber] = parseFloat(data[i][valueColumnName]);
|
||||
}
|
||||
|
||||
$.each(valueMap, function (index, value) {
|
||||
values.push(value);
|
||||
});
|
||||
dataTable.setData(values);
|
||||
} // draw the chart and return the chart object
|
||||
|
||||
|
||||
chart.draw(dataTable, plotSettings);
|
||||
return chart;
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
currentSettings.width = $('#resizer').width() - 20;
|
||||
currentSettings.height = $('#resizer').height() - 20; // TODO: a better way using .redraw() ?
|
||||
|
||||
if (currentChart !== null) {
|
||||
currentChart.destroy();
|
||||
}
|
||||
|
||||
var columnNames = [];
|
||||
$('#chartXAxisSelect option').each(function () {
|
||||
columnNames.push(Functions.escapeHtml($(this).text()));
|
||||
});
|
||||
|
||||
try {
|
||||
currentChart = queryChart(chartData, columnNames, currentSettings);
|
||||
|
||||
if (currentChart !== null) {
|
||||
$('#saveChart').attr('href', currentChart.toImageString());
|
||||
}
|
||||
} catch (err) {
|
||||
Functions.ajaxShowMessage(err.message, false);
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedSeries() {
|
||||
var val = $('#chartSeriesSelect').val() || [];
|
||||
var ret = [];
|
||||
$.each(val, function (i, v) {
|
||||
ret.push(parseInt(v, 10));
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function onXAxisChange() {
|
||||
var $xAxisSelect = $('#chartXAxisSelect');
|
||||
currentSettings.mainAxis = parseInt($xAxisSelect.val(), 10);
|
||||
|
||||
if (dateTimeCols.indexOf(currentSettings.mainAxis) !== -1) {
|
||||
document.getElementById('timelineChartType').classList.remove('d-none');
|
||||
} else {
|
||||
document.getElementById('timelineChartType').classList.add('d-none');
|
||||
|
||||
if (currentSettings.type === 'timeline') {
|
||||
$('#lineChartTypeRadio').prop('checked', true);
|
||||
currentSettings.type = 'line';
|
||||
}
|
||||
}
|
||||
|
||||
if (numericCols.indexOf(currentSettings.mainAxis) !== -1) {
|
||||
document.getElementById('scatterChartType').classList.remove('d-none');
|
||||
} else {
|
||||
document.getElementById('scatterChartType').classList.add('d-none');
|
||||
|
||||
if (currentSettings.type === 'scatter') {
|
||||
$('#lineChartTypeRadio').prop('checked', true);
|
||||
currentSettings.type = 'line';
|
||||
}
|
||||
}
|
||||
|
||||
var xAxisTitle = $xAxisSelect.children('option:selected').text();
|
||||
$('#xAxisLabelInput').val(xAxisTitle);
|
||||
currentSettings.xaxisLabel = xAxisTitle;
|
||||
}
|
||||
|
||||
function onDataSeriesChange() {
|
||||
var $seriesSelect = $('#chartSeriesSelect');
|
||||
currentSettings.selectedSeries = getSelectedSeries();
|
||||
var yAxisTitle;
|
||||
|
||||
if (currentSettings.selectedSeries.length === 1) {
|
||||
document.getElementById('pieChartType').classList.remove('d-none');
|
||||
yAxisTitle = $seriesSelect.children('option:selected').text();
|
||||
} else {
|
||||
document.getElementById('pieChartType').classList.add('d-none');
|
||||
|
||||
if (currentSettings.type === 'pie') {
|
||||
$('#lineChartTypeRadio').prop('checked', true);
|
||||
currentSettings.type = 'line';
|
||||
}
|
||||
|
||||
yAxisTitle = Messages.strYValues;
|
||||
}
|
||||
|
||||
$('#yAxisLabelInput').val(yAxisTitle);
|
||||
currentSettings.yaxisLabel = yAxisTitle;
|
||||
}
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
|
||||
|
||||
AJAX.registerTeardown('table/chart.js', function () {
|
||||
$('input[name="chartType"]').off('click');
|
||||
$('#barStackedCheckbox').off('click');
|
||||
$('#seriesColumnCheckbox').off('click');
|
||||
$('#chartTitleInput').off('focus').off('keyup').off('blur');
|
||||
$('#chartXAxisSelect').off('change');
|
||||
$('#chartSeriesSelect').off('change');
|
||||
$('#chartSeriesColumnSelect').off('change');
|
||||
$('#chartValueColumnSelect').off('change');
|
||||
$('#xAxisLabelInput').off('keyup');
|
||||
$('#yAxisLabelInput').off('keyup');
|
||||
$('#resizer').off('resizestop');
|
||||
$('#tblchartform').off('submit');
|
||||
});
|
||||
AJAX.registerOnload('table/chart.js', function () {
|
||||
// handle manual resize
|
||||
$('#resizer').on('resizestop', function () {
|
||||
// make room so that the handle will still appear
|
||||
$('#querychart').height($('#resizer').height() * 0.96);
|
||||
$('#querychart').width($('#resizer').width() * 0.96);
|
||||
|
||||
if (currentChart !== null) {
|
||||
currentChart.redraw({
|
||||
resetAxes: true
|
||||
});
|
||||
}
|
||||
}); // handle chart type changes
|
||||
|
||||
$('input[name="chartType"]').on('click', function () {
|
||||
var type = currentSettings.type = $(this).val();
|
||||
|
||||
if (type === 'bar' || type === 'column' || type === 'area') {
|
||||
document.getElementById('barStacked').classList.remove('d-none');
|
||||
} else {
|
||||
$('#barStackedCheckbox').prop('checked', false);
|
||||
$.extend(true, currentSettings, {
|
||||
stackSeries: false
|
||||
});
|
||||
document.getElementById('barStacked').classList.add('d-none');
|
||||
}
|
||||
|
||||
drawChart();
|
||||
}); // handle chosing alternative data format
|
||||
|
||||
$('#seriesColumnCheckbox').on('click', function () {
|
||||
var $seriesColumn = $('#chartSeriesColumnSelect');
|
||||
var $valueColumn = $('#chartValueColumnSelect');
|
||||
var $chartSeries = $('#chartSeriesSelect');
|
||||
|
||||
if ($(this).is(':checked')) {
|
||||
$seriesColumn.prop('disabled', false);
|
||||
$valueColumn.prop('disabled', false);
|
||||
$chartSeries.prop('disabled', true);
|
||||
currentSettings.seriesColumn = parseInt($seriesColumn.val(), 10);
|
||||
currentSettings.valueColumn = parseInt($valueColumn.val(), 10);
|
||||
} else {
|
||||
$seriesColumn.prop('disabled', true);
|
||||
$valueColumn.prop('disabled', true);
|
||||
$chartSeries.prop('disabled', false);
|
||||
currentSettings.seriesColumn = null;
|
||||
currentSettings.valueColumn = null;
|
||||
}
|
||||
|
||||
drawChart();
|
||||
}); // handle stacking for bar, column and area charts
|
||||
|
||||
$('#barStackedCheckbox').on('click', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$.extend(true, currentSettings, {
|
||||
stackSeries: true
|
||||
});
|
||||
} else {
|
||||
$.extend(true, currentSettings, {
|
||||
stackSeries: false
|
||||
});
|
||||
}
|
||||
|
||||
drawChart();
|
||||
}); // handle changes in chart title
|
||||
|
||||
$('#chartTitleInput').on('focus', function () {
|
||||
tempChartTitle = $(this).val();
|
||||
}).on('keyup', function () {
|
||||
currentSettings.title = $('#chartTitleInput').val();
|
||||
drawChart();
|
||||
}).on('blur', function () {
|
||||
if ($(this).val() !== tempChartTitle) {
|
||||
drawChart();
|
||||
}
|
||||
}); // handle changing the x-axis
|
||||
|
||||
$('#chartXAxisSelect').on('change', function () {
|
||||
onXAxisChange();
|
||||
drawChart();
|
||||
}); // handle changing the selected data series
|
||||
|
||||
$('#chartSeriesSelect').on('change', function () {
|
||||
onDataSeriesChange();
|
||||
drawChart();
|
||||
}); // handle changing the series column
|
||||
|
||||
$('#chartSeriesColumnSelect').on('change', function () {
|
||||
currentSettings.seriesColumn = parseInt($(this).val(), 10);
|
||||
drawChart();
|
||||
}); // handle changing the value column
|
||||
|
||||
$('#chartValueColumnSelect').on('change', function () {
|
||||
currentSettings.valueColumn = parseInt($(this).val(), 10);
|
||||
drawChart();
|
||||
}); // handle manual changes to the chart x-axis labels
|
||||
|
||||
$('#xAxisLabelInput').on('keyup', function () {
|
||||
currentSettings.xaxisLabel = $(this).val();
|
||||
drawChart();
|
||||
}); // handle manual changes to the chart y-axis labels
|
||||
|
||||
$('#yAxisLabelInput').on('keyup', function () {
|
||||
currentSettings.yaxisLabel = $(this).val();
|
||||
drawChart();
|
||||
}); // handler for ajax form submission
|
||||
|
||||
$('#tblchartform').on('submit', function () {
|
||||
var $form = $(this);
|
||||
|
||||
if (codeMirrorEditor) {
|
||||
$form[0].elements.sql_query.value = codeMirrorEditor.getValue();
|
||||
}
|
||||
|
||||
if (!Functions.checkSqlQuery($form[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var $msgbox = Functions.ajaxShowMessage();
|
||||
Functions.prepareForAjaxRequest($form);
|
||||
$.post($form.attr('action'), $form.serialize(), function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true && typeof data.chartData !== 'undefined') {
|
||||
chartData = JSON.parse(data.chartData);
|
||||
drawChart();
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}, 'json'); // end $.post()
|
||||
|
||||
return false;
|
||||
}); // from jQuery UI
|
||||
|
||||
$('#resizer').resizable({
|
||||
minHeight: 240,
|
||||
minWidth: 300
|
||||
}).width($('#div_view_options').width() - 50).trigger('resizestop');
|
||||
currentSettings = {
|
||||
type: 'line',
|
||||
width: $('#resizer').width() - 20,
|
||||
height: $('#resizer').height() - 20,
|
||||
xaxisLabel: $('#xAxisLabelInput').val(),
|
||||
yaxisLabel: $('#yAxisLabelInput').val(),
|
||||
title: $('#chartTitleInput').val(),
|
||||
stackSeries: false,
|
||||
mainAxis: parseInt($('#chartXAxisSelect').val(), 10),
|
||||
selectedSeries: getSelectedSeries(),
|
||||
seriesColumn: null
|
||||
};
|
||||
var vals = $('input[name="dateTimeCols"]').val().split(' ');
|
||||
$.each(vals, function (i, v) {
|
||||
dateTimeCols.push(parseInt(v, 10));
|
||||
});
|
||||
vals = $('input[name="numericCols"]').val().split(' ');
|
||||
$.each(vals, function (i, v) {
|
||||
numericCols.push(parseInt(v, 10));
|
||||
});
|
||||
onXAxisChange();
|
||||
onDataSeriesChange();
|
||||
$('#tblchartform').trigger('submit');
|
||||
});
|
@ -0,0 +1,310 @@
|
||||
/**
|
||||
* @fileoverview JavaScript functions used on /table/search
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires js/functions.js
|
||||
*/
|
||||
|
||||
/* global changeValueFieldType, verifyAfterSearchFieldChange */
|
||||
// js/table/change.js
|
||||
|
||||
/* global openGISEditor, gisEditorLoaded, loadJSAndGISEditor, loadGISEditor */
|
||||
// js/gis_data_editor.js
|
||||
var TableSelect = {};
|
||||
/**
|
||||
* Checks if given data-type is numeric or date.
|
||||
*
|
||||
* @param {string} dataType Column data-type
|
||||
*
|
||||
* @return {boolean | string}
|
||||
*/
|
||||
|
||||
TableSelect.checkIfDataTypeNumericOrDate = function (dataType) {
|
||||
// To test for numeric data-types.
|
||||
var numericRegExp = new RegExp('TINYINT|SMALLINT|MEDIUMINT|INT|BIGINT|DECIMAL|FLOAT|DOUBLE|REAL', 'i'); // To test for date data-types.
|
||||
|
||||
var dateRegExp = new RegExp('DATETIME|DATE|TIMESTAMP|TIME|YEAR', 'i'); // Return matched data-type
|
||||
|
||||
if (numericRegExp.test(dataType)) {
|
||||
return numericRegExp.exec(dataType)[0];
|
||||
}
|
||||
|
||||
if (dateRegExp.test(dataType)) {
|
||||
return dateRegExp.exec(dataType)[0];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
|
||||
|
||||
AJAX.registerTeardown('table/select.js', function () {
|
||||
$('#togglesearchformlink').off('click');
|
||||
$(document).off('submit', '#tbl_search_form.ajax');
|
||||
$('select.geom_func').off('change');
|
||||
$(document).off('click', 'span.open_search_gis_editor');
|
||||
$('body').off('change', 'select[name*="criteriaColumnOperators"]'); // Fix for bug #13778, changed 'click' to 'change'
|
||||
});
|
||||
AJAX.registerOnload('table/select.js', function () {
|
||||
/**
|
||||
* Prepare a div containing a link, otherwise it's incorrectly displayed
|
||||
* after a couple of clicks
|
||||
*/
|
||||
$('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>').insertAfter('#tbl_search_form') // don't show it until we have results on-screen
|
||||
.hide();
|
||||
$('#togglesearchformlink').html(Messages.strShowSearchCriteria).on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#tbl_search_form').slideToggle();
|
||||
|
||||
if ($link.text() === Messages.strHideSearchCriteria) {
|
||||
$link.text(Messages.strShowSearchCriteria);
|
||||
} else {
|
||||
$link.text(Messages.strHideSearchCriteria);
|
||||
} // avoid default click action
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
var tableRows = $('#fieldset_table_qbe select.column-operator');
|
||||
$.each(tableRows, function (index, item) {
|
||||
$(item).on('change', function () {
|
||||
changeValueFieldType(this, index);
|
||||
verifyAfterSearchFieldChange(index, '#tbl_search_form');
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Ajax event handler for Table search
|
||||
*/
|
||||
|
||||
$(document).on('submit', '#tbl_search_form.ajax', function (event) {
|
||||
var unaryFunctions = ['IS NULL', 'IS NOT NULL', '= \'\'', '!= \'\''];
|
||||
var geomUnaryFunctions = ['IsEmpty', 'IsSimple', 'IsRing', 'IsClosed']; // jQuery object to reuse
|
||||
|
||||
var $searchForm = $(this);
|
||||
event.preventDefault(); // empty previous search results while we are waiting for new results
|
||||
|
||||
$('#sqlqueryresultsouter').empty();
|
||||
var $msgbox = Functions.ajaxShowMessage(Messages.strSearching, false);
|
||||
Functions.prepareForAjaxRequest($searchForm);
|
||||
var values = {};
|
||||
$searchForm.find(':input').each(function () {
|
||||
var $input = $(this);
|
||||
|
||||
if ($input.attr('type') === 'checkbox' || $input.attr('type') === 'radio') {
|
||||
if ($input.is(':checked')) {
|
||||
values[this.name] = $input.val();
|
||||
}
|
||||
} else {
|
||||
values[this.name] = $input.val();
|
||||
}
|
||||
});
|
||||
var columnCount = $('select[name="columnsToDisplay[]"] option').length; // Submit values only for the columns that have unary column operator or a search criteria
|
||||
|
||||
for (var a = 0; a < columnCount; a++) {
|
||||
if ($.inArray(values['criteriaColumnOperators[' + a + ']'], unaryFunctions) >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (values['geom_func[' + a + ']'] && $.inArray(values['geom_func[' + a + ']'], geomUnaryFunctions) >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (values['criteriaValues[' + a + ']'] === '' || values['criteriaValues[' + a + ']'] === null) {
|
||||
delete values['criteriaValues[' + a + ']'];
|
||||
delete values['criteriaColumnOperators[' + a + ']'];
|
||||
delete values['criteriaColumnNames[' + a + ']'];
|
||||
delete values['criteriaColumnTypes[' + a + ']'];
|
||||
delete values['criteriaColumnCollations[' + a + ']'];
|
||||
}
|
||||
} // If all columns are selected, use a single parameter to indicate that
|
||||
|
||||
|
||||
if (values['columnsToDisplay[]'] !== null) {
|
||||
if (values['columnsToDisplay[]'].length === columnCount) {
|
||||
delete values['columnsToDisplay[]'];
|
||||
values.displayAllColumns = true;
|
||||
}
|
||||
} else {
|
||||
values.displayAllColumns = true;
|
||||
}
|
||||
|
||||
$.post($searchForm.attr('action'), values, function (data) {
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
if (typeof data.sql_query !== 'undefined') {
|
||||
// zero rows
|
||||
$('#sqlqueryresultsouter').html(data.sql_query);
|
||||
} else {
|
||||
// results found
|
||||
$('#sqlqueryresultsouter').html(data.message);
|
||||
$('.sqlqueryresults').trigger('makegrid');
|
||||
}
|
||||
|
||||
$('#tbl_search_form') // workaround for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome.
|
||||
.slideToggle().hide();
|
||||
$('#togglesearchformlink') // always start with the Show message
|
||||
.text(Messages.strShowSearchCriteria);
|
||||
$('#togglesearchformdiv') // now it's time to show the div containing the link
|
||||
.show();
|
||||
$('html, body').animate({
|
||||
scrollTop: 0
|
||||
}, 'fast');
|
||||
} else {
|
||||
$('#sqlqueryresultsouter').html(data.error);
|
||||
}
|
||||
|
||||
Functions.highlightSql($('#sqlqueryresultsouter'));
|
||||
}); // end $.post()
|
||||
}); // Following section is related to the 'function based search' for geometry data types.
|
||||
// Initially hide all the open_gis_editor spans
|
||||
|
||||
$('span.open_search_gis_editor').hide();
|
||||
$('select.geom_func').on('change', function () {
|
||||
var $geomFuncSelector = $(this);
|
||||
var binaryFunctions = ['Contains', 'Crosses', 'Disjoint', 'Equals', 'Intersects', 'Overlaps', 'Touches', 'Within', 'MBRContains', 'MBRDisjoint', 'MBREquals', 'MBRIntersects', 'MBROverlaps', 'MBRTouches', 'MBRWithin', 'ST_Contains', 'ST_Crosses', 'ST_Disjoint', 'ST_Equals', 'ST_Intersects', 'ST_Overlaps', 'ST_Touches', 'ST_Within'];
|
||||
var tempArray = ['Envelope', 'EndPoint', 'StartPoint', 'ExteriorRing', 'Centroid', 'PointOnSurface'];
|
||||
var outputGeomFunctions = binaryFunctions.concat(tempArray); // If the chosen function takes two geometry objects as parameters
|
||||
|
||||
var $operator = $geomFuncSelector.parents('tr').find('td').eq(4).find('select');
|
||||
|
||||
if ($.inArray($geomFuncSelector.val(), binaryFunctions) >= 0) {
|
||||
$operator.prop('readonly', true);
|
||||
} else {
|
||||
$operator.prop('readonly', false);
|
||||
} // if the chosen function's output is a geometry, enable GIS editor
|
||||
|
||||
|
||||
var $editorSpan = $geomFuncSelector.parents('tr').find('span.open_search_gis_editor');
|
||||
|
||||
if ($.inArray($geomFuncSelector.val(), outputGeomFunctions) >= 0) {
|
||||
$editorSpan.show();
|
||||
} else {
|
||||
$editorSpan.hide();
|
||||
}
|
||||
});
|
||||
$(document).on('click', 'span.open_search_gis_editor', function (event) {
|
||||
event.preventDefault();
|
||||
var $span = $(this); // Current value
|
||||
|
||||
var value = $span.parent('td').children('input[type=\'text\']').val(); // Field name
|
||||
|
||||
var field = 'Parameter'; // Column type
|
||||
|
||||
var geomFunc = $span.parents('tr').find('.geom_func').val();
|
||||
var type;
|
||||
|
||||
if (geomFunc === 'Envelope') {
|
||||
type = 'polygon';
|
||||
} else if (geomFunc === 'ExteriorRing') {
|
||||
type = 'linestring';
|
||||
} else {
|
||||
type = 'point';
|
||||
} // Names of input field and null checkbox
|
||||
|
||||
|
||||
var inputName = $span.parent('td').children('input[type=\'text\']').attr('name'); // Token
|
||||
|
||||
openGISEditor();
|
||||
|
||||
if (!gisEditorLoaded) {
|
||||
loadJSAndGISEditor(value, field, type, inputName);
|
||||
} else {
|
||||
loadGISEditor(value, field, type, inputName);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Ajax event handler for Range-Search.
|
||||
*/
|
||||
|
||||
$('body').on('change', 'select[name*="criteriaColumnOperators"]', function () {
|
||||
// Fix for bug #13778, changed 'click' to 'change'
|
||||
var $sourceSelect = $(this); // Get the column name.
|
||||
|
||||
var columnName = $(this).closest('tr').find('th').first().text(); // Get the data-type of column excluding size.
|
||||
|
||||
var dataType = $(this).closest('tr').find('td[data-type]').attr('data-type');
|
||||
dataType = TableSelect.checkIfDataTypeNumericOrDate(dataType); // Get the operator.
|
||||
|
||||
var operator = $(this).val();
|
||||
|
||||
if ((operator === 'BETWEEN' || operator === 'NOT BETWEEN') && dataType) {
|
||||
var $msgbox = Functions.ajaxShowMessage();
|
||||
$.ajax({
|
||||
url: 'index.php?route=/table/search',
|
||||
type: 'POST',
|
||||
data: {
|
||||
'server': CommonParams.get('server'),
|
||||
'ajax_request': 1,
|
||||
'db': $('input[name="db"]').val(),
|
||||
'table': $('input[name="table"]').val(),
|
||||
'column': columnName,
|
||||
'range_search': 1
|
||||
},
|
||||
success: function (response) {
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
|
||||
if (response.success) {
|
||||
// Get the column min value.
|
||||
var min = response.column_data.min ? '(' + Messages.strColumnMin + ' ' + response.column_data.min + ')' : ''; // Get the column max value.
|
||||
|
||||
var max = response.column_data.max ? '(' + Messages.strColumnMax + ' ' + response.column_data.max + ')' : '';
|
||||
$('#rangeSearchModal').modal('show');
|
||||
$('#rangeSearchLegend').first().html(operator);
|
||||
$('#rangeSearchMin').first().text(min);
|
||||
$('#rangeSearchMax').first().text(max); // Reset input values on reuse
|
||||
|
||||
$('#min_value').first().val('');
|
||||
$('#max_value').first().val(''); // Add datepicker wherever required.
|
||||
|
||||
Functions.addDatepicker($('#min_value'), dataType);
|
||||
Functions.addDatepicker($('#max_value'), dataType);
|
||||
$('#rangeSearchModalGo').on('click', function () {
|
||||
var minValue = $('#min_value').val();
|
||||
var maxValue = $('#max_value').val();
|
||||
var finalValue = '';
|
||||
|
||||
if (minValue.length && maxValue.length) {
|
||||
finalValue = minValue + ', ' + maxValue;
|
||||
}
|
||||
|
||||
var $targetField = $sourceSelect.closest('tr').find('[name*="criteriaValues"]'); // If target field is a select list.
|
||||
|
||||
if ($targetField.is('select')) {
|
||||
$targetField.val(finalValue);
|
||||
var $options = $targetField.find('option');
|
||||
var $closestMin = null;
|
||||
var $closestMax = null; // Find closest min and max value.
|
||||
|
||||
$options.each(function () {
|
||||
if ($closestMin === null || Math.abs($(this).val() - minValue) < Math.abs($closestMin.val() - minValue)) {
|
||||
$closestMin = $(this);
|
||||
}
|
||||
|
||||
if ($closestMax === null || Math.abs($(this).val() - maxValue) < Math.abs($closestMax.val() - maxValue)) {
|
||||
$closestMax = $(this);
|
||||
}
|
||||
});
|
||||
$closestMin.attr('selected', 'selected');
|
||||
$closestMax.attr('selected', 'selected');
|
||||
} else {
|
||||
$targetField.val(finalValue);
|
||||
}
|
||||
|
||||
$('#rangeSearchModal').modal('hide');
|
||||
});
|
||||
} else {
|
||||
Functions.ajaxShowMessage(response.error);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
Functions.ajaxShowMessage(Messages.strErrorProcessingRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
var windowWidth = $(window).width();
|
||||
$('.jsresponsive').css('max-width', windowWidth - 69 + 'px');
|
||||
});
|
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* SQL syntax highlighting transformation plugin js
|
||||
*
|
||||
* @package PhpMyAdmin
|
||||
*/
|
||||
AJAX.registerOnload('transformations/sql_editor.js', function () {
|
||||
$('textarea.transform_sql_editor').each(function () {
|
||||
Functions.getSqlEditor($(this), {}, 'both');
|
||||
});
|
||||
});
|
@ -0,0 +1,90 @@
|
||||
/* global u2f */
|
||||
// js/vendor/u2f-api-polyfill.js
|
||||
AJAX.registerOnload('u2f.js', function () {
|
||||
var $inputReg = $('#u2f_registration_response');
|
||||
|
||||
if ($inputReg.length > 0) {
|
||||
var $formReg = $inputReg.parents('form');
|
||||
$formReg.find('input[type=submit]').hide();
|
||||
setTimeout(function () {
|
||||
// A magic JS function that talks to the USB device. This function will keep polling for the USB device until it finds one.
|
||||
var request = JSON.parse($inputReg.attr('data-request'));
|
||||
u2f.register(request.appId, [request], JSON.parse($inputReg.attr('data-signatures')), function (data) {
|
||||
// Handle returning error data
|
||||
if (data.errorCode && data.errorCode !== 0) {
|
||||
switch (data.errorCode) {
|
||||
case 5:
|
||||
Functions.ajaxShowMessage(Messages.strU2FTimeout, false, 'error');
|
||||
break;
|
||||
|
||||
case 4:
|
||||
Functions.ajaxShowMessage(Messages.strU2FErrorRegister, false, 'error');
|
||||
break;
|
||||
|
||||
case 3:
|
||||
Functions.ajaxShowMessage(Messages.strU2FInvalidClient, false, 'error');
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Functions.ajaxShowMessage(Messages.strU2FBadRequest, false, 'error');
|
||||
break;
|
||||
|
||||
default:
|
||||
Functions.ajaxShowMessage(Messages.strU2FUnknown, false, 'error');
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
} // Fill and submit form.
|
||||
|
||||
|
||||
$inputReg.val(JSON.stringify(data));
|
||||
$formReg.trigger('submit');
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
var $inputAuth = $('#u2f_authentication_response');
|
||||
|
||||
if ($inputAuth.length > 0) {
|
||||
var $formAuth = $inputAuth.parents('form');
|
||||
$formAuth.find('input[type=submit]').hide();
|
||||
setTimeout(function () {
|
||||
// Magic JavaScript talking to your HID
|
||||
// appid, challenge, authenticateRequests
|
||||
var request = JSON.parse($inputAuth.attr('data-request'));
|
||||
u2f.sign(request[0].appId, request[0].challenge, request, function (data) {
|
||||
// Handle returning error data
|
||||
if (data.errorCode && data.errorCode !== 0) {
|
||||
switch (data.errorCode) {
|
||||
case 5:
|
||||
Functions.ajaxShowMessage(Messages.strU2FTimeout, false, 'error');
|
||||
break;
|
||||
|
||||
case 4:
|
||||
Functions.ajaxShowMessage(Messages.strU2FErrorAuthenticate, false, 'error');
|
||||
break;
|
||||
|
||||
case 3:
|
||||
Functions.ajaxShowMessage(Messages.strU2FInvalidClient, false, 'error');
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Functions.ajaxShowMessage(Messages.strU2FBadRequest, false, 'error');
|
||||
break;
|
||||
|
||||
default:
|
||||
Functions.ajaxShowMessage(Messages.strU2FUnknown, false, 'error');
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
} // Fill and submit form.
|
||||
|
||||
|
||||
$inputAuth.val(JSON.stringify(data));
|
||||
$formAuth.trigger('submit');
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Conditionally included if framing is not allowed
|
||||
*/
|
||||
if (self === top) {
|
||||
var styleElement = document.getElementById('cfs-style');
|
||||
// check if styleElement has already been removed
|
||||
// to avoid frequently reported js error
|
||||
if (typeof(styleElement) !== 'undefined' && styleElement !== null) {
|
||||
styleElement.parentNode.removeChild(styleElement);
|
||||
}
|
||||
} else {
|
||||
top.location = self.location;
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
/**
|
||||
* JavaScript functions used on Database Search page
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires js/functions.js
|
||||
*
|
||||
* @package PhpMyAdmin
|
||||
*/
|
||||
|
||||
/* global makeGrid */ // js/makegrid.js
|
||||
|
||||
/**
|
||||
* AJAX script for the Database Search page.
|
||||
*
|
||||
* Actions ajaxified here:
|
||||
* Retrieve result of SQL query
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('database/search.js', function () {
|
||||
$('a.browse_results').off('click');
|
||||
$('a.delete_results').off('click');
|
||||
$('#buttonGo').off('click');
|
||||
$('#togglesearchresultlink').off('click');
|
||||
$('#togglequerybox').off('click');
|
||||
$('#togglesearchformlink').off('click');
|
||||
$('#select_all').off('click');
|
||||
$('#unselect_all').off('click');
|
||||
$(document).off('submit', '#db_search_form.ajax');
|
||||
});
|
||||
|
||||
AJAX.registerOnload('database/search.js', function () {
|
||||
/** Hide the table link in the initial search result */
|
||||
var icon = Functions.getImage('s_tbl', '', { 'id': 'table-image' }).toString();
|
||||
$('#table-info').prepend(icon).hide();
|
||||
|
||||
/** Hide the browse and deleted results in the new search criteria */
|
||||
$('#buttonGo').on('click', function () {
|
||||
$('#table-info').hide();
|
||||
$('#browse-results').hide();
|
||||
$('#sqlqueryform').hide();
|
||||
$('#togglequerybox').hide();
|
||||
});
|
||||
/**
|
||||
* Prepare a div containing a link for toggle the search results
|
||||
*/
|
||||
$('#togglesearchresultsdiv')
|
||||
/** don't show it until we have results on-screen */
|
||||
.hide();
|
||||
|
||||
/**
|
||||
* Changing the displayed text according to
|
||||
* the hide/show criteria in search result forms
|
||||
*/
|
||||
$('#togglesearchresultlink')
|
||||
.html(Messages.strHideSearchResults)
|
||||
.on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#searchresults').slideToggle();
|
||||
if ($link.text() === Messages.strHideSearchResults) {
|
||||
$link.text(Messages.strShowSearchResults);
|
||||
} else {
|
||||
$link.text(Messages.strHideSearchResults);
|
||||
}
|
||||
/** avoid default click action */
|
||||
return false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Prepare a div containing a link for toggle the search form,
|
||||
* otherwise it's incorrectly displayed after a couple of clicks
|
||||
*/
|
||||
$('#togglesearchformdiv')
|
||||
.hide(); // don't show it until we have results on-screen
|
||||
|
||||
/**
|
||||
* Changing the displayed text according to
|
||||
* the hide/show criteria in search form
|
||||
*/
|
||||
$('#togglequerybox')
|
||||
.hide()
|
||||
.on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#sqlqueryform').slideToggle('medium');
|
||||
if ($link.text() === Messages.strHideQueryBox) {
|
||||
$link.text(Messages.strShowQueryBox);
|
||||
} else {
|
||||
$link.text(Messages.strHideQueryBox);
|
||||
}
|
||||
/** avoid default click action */
|
||||
return false;
|
||||
});
|
||||
|
||||
/** don't show it until we have results on-screen */
|
||||
|
||||
/**
|
||||
* Changing the displayed text according to
|
||||
* the hide/show criteria in search criteria form
|
||||
*/
|
||||
$('#togglesearchformlink')
|
||||
.html(Messages.strShowSearchCriteria)
|
||||
.on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#db_search_form').slideToggle();
|
||||
if ($link.text() === Messages.strHideSearchCriteria) {
|
||||
$link.text(Messages.strShowSearchCriteria);
|
||||
} else {
|
||||
$link.text(Messages.strHideSearchCriteria);
|
||||
}
|
||||
/** avoid default click action */
|
||||
return false;
|
||||
});
|
||||
|
||||
/*
|
||||
* Ajax Event handler for retrieving the results from a table
|
||||
*/
|
||||
$(document).on('click', 'a.browse_results', function (e) {
|
||||
e.preventDefault();
|
||||
/** Hides the results shown by the delete criteria */
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strBrowsing, false);
|
||||
$('#sqlqueryform').hide();
|
||||
$('#togglequerybox').hide();
|
||||
/** Load the browse results to the page */
|
||||
$('#table-info').show();
|
||||
var tableName = $(this).data('table-name');
|
||||
$('#table-link').attr({ 'href' : $(this).attr('href') }).text(tableName);
|
||||
|
||||
var url = $(this).attr('href') + '#searchresults';
|
||||
var browseSql = $(this).data('browse-sql');
|
||||
var params = {
|
||||
'ajax_request': true,
|
||||
'is_js_confirmed': true,
|
||||
'sql_query' : browseSql
|
||||
};
|
||||
$.post(url, params, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success) {
|
||||
$('#browse-results').html(data.message);
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
$('.table_results').each(function () {
|
||||
makeGrid(this, true, true, true, true);
|
||||
});
|
||||
$('#browse-results').show();
|
||||
Functions.highlightSql($('#browse-results'));
|
||||
$('html, body')
|
||||
.animate({
|
||||
scrollTop: $('#browse-results').offset().top
|
||||
}, 1000);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* Ajax Event handler for deleting the results from a table
|
||||
*/
|
||||
$(document).on('click', 'a.delete_results', function (e) {
|
||||
e.preventDefault();
|
||||
/** Hides the results shown by the browse criteria */
|
||||
$('#table-info').hide();
|
||||
$('#sqlqueryform').hide();
|
||||
$('#togglequerybox').hide();
|
||||
/** Conformation message for deletion */
|
||||
var msg = Functions.sprintf(
|
||||
Messages.strConfirmDeleteResults,
|
||||
$(this).data('table-name')
|
||||
);
|
||||
if (confirm(msg)) {
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strDeleting, false);
|
||||
/** Load the deleted option to the page*/
|
||||
$('#sqlqueryform').html('');
|
||||
var params = {
|
||||
'ajax_request': true,
|
||||
'is_js_confirmed': true,
|
||||
'sql_query': $(this).data('delete-sql')
|
||||
};
|
||||
var url = $(this).attr('href');
|
||||
|
||||
$.post(url, params, function (data) {
|
||||
if (typeof data === 'undefined' || !data.success) {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
$('#sqlqueryform').html(data.sql_query);
|
||||
/** Refresh the search results after the deletion */
|
||||
$('#buttonGo').trigger('click');
|
||||
$('#togglequerybox').html(Messages.strHideQueryBox);
|
||||
/** Show the results of the deletion option */
|
||||
$('#browse-results').hide();
|
||||
$('#sqlqueryform').show();
|
||||
$('#togglequerybox').show();
|
||||
$('html, body')
|
||||
.animate({
|
||||
scrollTop: $('#browse-results').offset().top
|
||||
}, 1000);
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Ajax Event handler for retrieving the result of an SQL Query
|
||||
*/
|
||||
$(document).on('submit', '#db_search_form.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
if ($('#criteriaTables :selected').length === 0) {
|
||||
Functions.ajaxShowMessage(Messages.strNoTableSelected);
|
||||
return;
|
||||
}
|
||||
var $msgbox = Functions.ajaxShowMessage(Messages.strSearching, false);
|
||||
// jQuery object to reuse
|
||||
var $form = $(this);
|
||||
|
||||
Functions.prepareForAjaxRequest($form);
|
||||
|
||||
var url = $form.serialize() + CommonParams.get('arg_separator') + 'submit_search=' + $('#buttonGo').val();
|
||||
$.post($form.attr('action'), url, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
// found results
|
||||
$('#searchresults').html(data.message);
|
||||
|
||||
$('#togglesearchresultlink')
|
||||
// always start with the Show message
|
||||
.text(Messages.strHideSearchResults);
|
||||
$('#togglesearchresultsdiv')
|
||||
// now it's time to show the div containing the link
|
||||
.show();
|
||||
$('#searchresults').show();
|
||||
|
||||
|
||||
$('#db_search_form')
|
||||
// workaround for Chrome problem (bug #3168569)
|
||||
.slideToggle()
|
||||
.hide();
|
||||
$('#togglesearchformlink')
|
||||
// always start with the Show message
|
||||
.text(Messages.strShowSearchCriteria);
|
||||
$('#togglesearchformdiv')
|
||||
// now it's time to show the div containing the link
|
||||
.show();
|
||||
} else {
|
||||
// error message (zero rows)
|
||||
$('#searchresults').html(data.error).show();
|
||||
}
|
||||
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
});
|
||||
});
|
||||
|
||||
$('#select_all').on('click', function () {
|
||||
Functions.setSelectOptions('db_search', 'criteriaTables[]', true);
|
||||
return false;
|
||||
});
|
||||
$('#unselect_all').on('click', function () {
|
||||
Functions.setSelectOptions('db_search', 'criteriaTables[]', false);
|
||||
return false;
|
||||
});
|
||||
}); // end $()
|
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Unbind all event handlers before tearing down the page
|
||||
*/
|
||||
AJAX.registerTeardown('database/tracking.js', function () {
|
||||
$('body').off('click', '#trackedForm.ajax button[name="submit_mult"], #trackedForm.ajax input[name="submit_mult"]');
|
||||
$('body').off('click', '#untrackedForm.ajax button[name="submit_mult"], #untrackedForm.ajax input[name="submit_mult"]');
|
||||
$('body').off('click', 'a.delete_tracking_anchor.ajax');
|
||||
});
|
||||
|
||||
/**
|
||||
* Bind event handlers
|
||||
*/
|
||||
AJAX.registerOnload('database/tracking.js', function () {
|
||||
var $versions = $('#versions');
|
||||
$versions.find('tr').first().find('th').append($('<div class="sorticon"></div>'));
|
||||
$versions.tablesorter({
|
||||
sortList: [[1, 0]],
|
||||
headers: {
|
||||
0: { sorter: false },
|
||||
2: { sorter: 'integer' },
|
||||
5: { sorter: false },
|
||||
6: { sorter: false },
|
||||
7: { sorter: false }
|
||||
}
|
||||
});
|
||||
|
||||
var $noVersions = $('#noversions');
|
||||
$noVersions.find('tr').first().find('th').append($('<div class="sorticon"></div>'));
|
||||
$noVersions.tablesorter({
|
||||
sortList: [[1, 0]],
|
||||
headers: {
|
||||
0: { sorter: false },
|
||||
2: { sorter: false }
|
||||
}
|
||||
});
|
||||
|
||||
var $body = $('body');
|
||||
|
||||
/**
|
||||
* Handles multi submit for tracked tables
|
||||
*/
|
||||
$body.on('click', '#trackedForm.ajax button[name="submit_mult"], #trackedForm.ajax input[name="submit_mult"]', function (e) {
|
||||
e.preventDefault();
|
||||
var $button = $(this);
|
||||
var $form = $button.parent('form');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val();
|
||||
|
||||
if ($button.val() === 'delete_tracking') {
|
||||
var question = Messages.strDeleteTrackingDataMultiple;
|
||||
$button.confirm(question, $form.attr('action'), function (url) {
|
||||
Functions.ajaxShowMessage(Messages.strDeletingTrackingData);
|
||||
AJAX.source = $form;
|
||||
$.post(url, submitData, AJAX.responseHandler);
|
||||
});
|
||||
} else {
|
||||
Functions.ajaxShowMessage();
|
||||
AJAX.source = $form;
|
||||
$.post($form.attr('action'), submitData, AJAX.responseHandler);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles multi submit for untracked tables
|
||||
*/
|
||||
$body.on('click', '#untrackedForm.ajax button[name="submit_mult"], #untrackedForm.ajax input[name="submit_mult"]', function (e) {
|
||||
e.preventDefault();
|
||||
var $button = $(this);
|
||||
var $form = $button.parent('form');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep + 'submit_mult=' + $button.val();
|
||||
Functions.ajaxShowMessage();
|
||||
AJAX.source = $form;
|
||||
$.post($form.attr('action'), submitData, AJAX.responseHandler);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ajax Event handler for 'Delete tracking'
|
||||
*/
|
||||
$body.on('click', 'a.delete_tracking_anchor.ajax', function (e) {
|
||||
e.preventDefault();
|
||||
var $anchor = $(this);
|
||||
var question = Messages.strDeleteTrackingData;
|
||||
$anchor.confirm(question, $anchor.attr('href'), function (url) {
|
||||
Functions.ajaxShowMessage(Messages.strDeletingTrackingData);
|
||||
AJAX.source = $anchor;
|
||||
var argSep = CommonParams.get('arg_separator');
|
||||
var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData());
|
||||
params += argSep + 'ajax_page_request=1';
|
||||
$.post(url, params, AJAX.responseHandler);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,530 @@
|
||||
AJAX.registerTeardown('database/triggers.js', function () {
|
||||
$(document).off('click', 'a.ajax.add_anchor, a.ajax.edit_anchor');
|
||||
$(document).off('click', 'a.ajax.export_anchor');
|
||||
$(document).off('click', '#bulkActionExportButton');
|
||||
$(document).off('click', 'a.ajax.drop_anchor');
|
||||
$(document).off('click', '#bulkActionDropButton');
|
||||
});
|
||||
|
||||
const DatabaseTriggers = {
|
||||
/**
|
||||
* @var $ajaxDialog Query object containing the reference to the
|
||||
* dialog that contains the editor
|
||||
*/
|
||||
$ajaxDialog: null,
|
||||
/**
|
||||
* @var syntaxHiglighter Reference to the codemirror editor
|
||||
*/
|
||||
syntaxHiglighter: null,
|
||||
/**
|
||||
* @var buttonOptions Object containing options for
|
||||
* the jQueryUI dialog buttons
|
||||
*/
|
||||
buttonOptions: {},
|
||||
/**
|
||||
* Validate editor form fields.
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
validate: function () {
|
||||
/**
|
||||
* @var $elm a jQuery object containing the reference
|
||||
* to an element that is being validated
|
||||
*/
|
||||
var $elm = null;
|
||||
// Common validation. At the very least the name
|
||||
// and the definition must be provided for an item
|
||||
$elm = $('table.rte_table').last().find('input[name=item_name]');
|
||||
if ($elm.val() === '') {
|
||||
$elm.trigger('focus');
|
||||
alert(Messages.strFormEmpty);
|
||||
return false;
|
||||
}
|
||||
$elm = $('table.rte_table').find('textarea[name=item_definition]');
|
||||
if ($elm.val() === '') {
|
||||
if (this.syntaxHiglighter !== null) {
|
||||
this.syntaxHiglighter.focus();
|
||||
} else {
|
||||
$('textarea[name=item_definition]').last().trigger('focus');
|
||||
}
|
||||
alert(Messages.strFormEmpty);
|
||||
return false;
|
||||
}
|
||||
// The validation has so far passed, so now
|
||||
// we can validate item-specific fields.
|
||||
return this.validateCustom();
|
||||
}, // end validate()
|
||||
/**
|
||||
* Validate custom editor form fields.
|
||||
* This function can be overridden by
|
||||
* other files in this folder
|
||||
*
|
||||
* @return {bool}
|
||||
*/
|
||||
validateCustom: function () {
|
||||
return true;
|
||||
}, // end validateCustom()
|
||||
|
||||
exportDialog: function ($this) {
|
||||
var $msg = Functions.ajaxShowMessage();
|
||||
if ($this.attr('id') === 'bulkActionExportButton') {
|
||||
var combined = {
|
||||
success: true,
|
||||
title: Messages.strExport,
|
||||
message: '',
|
||||
error: ''
|
||||
};
|
||||
// export anchors of all selected rows
|
||||
var exportAnchors = $('input.checkall:checked').parents('tr').find('.export_anchor');
|
||||
var count = exportAnchors.length;
|
||||
var returnCount = 0;
|
||||
var p = $.when();
|
||||
exportAnchors.each(function () {
|
||||
var h = $(this).attr('href');
|
||||
p = p.then(function () {
|
||||
return $.get(h, { 'ajax_request': true }, function (data) {
|
||||
returnCount++;
|
||||
if (data.success === true) {
|
||||
combined.message += '\n' + data.message + '\n';
|
||||
if (returnCount === count) {
|
||||
showExport(combined);
|
||||
}
|
||||
} else {
|
||||
// complain even if one export is failing
|
||||
combined.success = false;
|
||||
combined.error += '\n' + data.error + '\n';
|
||||
if (returnCount === count) {
|
||||
showExport(combined);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$.get($this.attr('href'), { 'ajax_request': true }, showExport);
|
||||
}
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
|
||||
function showExport (data) {
|
||||
if (data.success === true) {
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
/**
|
||||
* @var button_options Object containing options
|
||||
* for jQueryUI dialog buttons
|
||||
*/
|
||||
var buttonOptions = {};
|
||||
buttonOptions[Messages.strClose] = function () {
|
||||
$(this).dialog('close').remove();
|
||||
};
|
||||
/**
|
||||
* Display the dialog to the user
|
||||
*/
|
||||
data.message = '<textarea cols="40" rows="15" class="w-100">' + data.message + '</textarea>';
|
||||
var $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
|
||||
width: 500,
|
||||
buttons: buttonOptions,
|
||||
title: data.title
|
||||
});
|
||||
// Attach syntax highlighted editor to export dialog
|
||||
/**
|
||||
* @var $elm jQuery object containing the reference
|
||||
* to the Export textarea.
|
||||
*/
|
||||
var $elm = $ajaxDialog.find('textarea');
|
||||
Functions.getSqlEditor($elm);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
} // end showExport()
|
||||
}, // end exportDialog()
|
||||
editorDialog: function (isNew, $this) {
|
||||
var that = this;
|
||||
/**
|
||||
* @var $edit_row jQuery object containing the reference to
|
||||
* the row of the the item being edited
|
||||
* from the list of items
|
||||
*/
|
||||
var $editRow = null;
|
||||
if ($this.hasClass('edit_anchor')) {
|
||||
// Remember the row of the item being edited for later,
|
||||
// so that if the edit is successful, we can replace the
|
||||
// row with info about the modified item.
|
||||
$editRow = $this.parents('tr');
|
||||
}
|
||||
/**
|
||||
* @var $msg jQuery object containing the reference to
|
||||
* the AJAX message shown to the user
|
||||
*/
|
||||
var $msg = Functions.ajaxShowMessage();
|
||||
$.get($this.attr('href'), { 'ajax_request': true }, function (data) {
|
||||
if (data.success === true) {
|
||||
// We have successfully fetched the editor form
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
// Now define the function that is called when
|
||||
// the user presses the "Go" button
|
||||
that.buttonOptions[Messages.strGo] = function () {
|
||||
// Move the data from the codemirror editor back to the
|
||||
// textarea, where it can be used in the form submission.
|
||||
if (typeof CodeMirror !== 'undefined') {
|
||||
that.syntaxHiglighter.save();
|
||||
}
|
||||
// Validate editor and submit request, if passed.
|
||||
if (that.validate()) {
|
||||
/**
|
||||
* @var data Form data to be sent in the AJAX request
|
||||
*/
|
||||
var data = $('form.rte_form').last().serialize();
|
||||
$msg = Functions.ajaxShowMessage(
|
||||
Messages.strProcessingRequest
|
||||
);
|
||||
var url = $('form.rte_form').last().attr('action');
|
||||
$.post(url, data, function (data) {
|
||||
if (data.success === true) {
|
||||
// Item created successfully
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
Functions.slidingMessage(data.message);
|
||||
that.$ajaxDialog.dialog('close');
|
||||
// If we are in 'edit' mode, we must
|
||||
// remove the reference to the old row.
|
||||
if (mode === 'edit' && $editRow !== null) {
|
||||
$editRow.remove();
|
||||
}
|
||||
// Sometimes, like when moving a trigger from
|
||||
// a table to another one, the new row should
|
||||
// not be inserted into the list. In this case
|
||||
// "data.insert" will be set to false.
|
||||
if (data.insert) {
|
||||
// Insert the new row at the correct
|
||||
// location in the list of items
|
||||
/**
|
||||
* @var text Contains the name of an item from
|
||||
* the list that is used in comparisons
|
||||
* to find the correct location where
|
||||
* to insert a new row.
|
||||
*/
|
||||
var text = '';
|
||||
/**
|
||||
* @var inserted Whether a new item has been
|
||||
* inserted in the list or not
|
||||
*/
|
||||
var inserted = false;
|
||||
$('table.data').find('tr').each(function () {
|
||||
text = $(this)
|
||||
.children('td')
|
||||
.eq(0)
|
||||
.find('strong')
|
||||
.text()
|
||||
.toUpperCase()
|
||||
.trim();
|
||||
if (text !== '' && text > data.name) {
|
||||
$(this).before(data.new_row);
|
||||
inserted = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (! inserted) {
|
||||
// If we didn't manage to insert the row yet,
|
||||
// it must belong at the end of the list,
|
||||
// so we insert it there.
|
||||
$('table.data').append(data.new_row);
|
||||
}
|
||||
// Fade-in the new row
|
||||
$('tr.ajaxInsert')
|
||||
.show('slow')
|
||||
.removeClass('ajaxInsert');
|
||||
} else if ($('table.data').find('tr').has('td').length === 0) {
|
||||
// If we are not supposed to insert the new row,
|
||||
// we will now check if the table is empty and
|
||||
// needs to be hidden. This will be the case if
|
||||
// we were editing the only item in the list,
|
||||
// which we removed and will not be inserting
|
||||
// something else in its place.
|
||||
$('table.data').hide('slow', function () {
|
||||
$('#nothing2display').show('slow');
|
||||
});
|
||||
}
|
||||
// Now we have inserted the row at the correct
|
||||
// position, but surely at least some row classes
|
||||
// are wrong now. So we will iterate through
|
||||
// all rows and assign correct classes to them
|
||||
/**
|
||||
* @var ct Count of processed rows
|
||||
*/
|
||||
var ct = 0;
|
||||
/**
|
||||
* @var rowclass Class to be attached to the row
|
||||
* that is being processed
|
||||
*/
|
||||
var rowclass = '';
|
||||
$('table.data').find('tr').has('td').each(function () {
|
||||
rowclass = (ct % 2 === 0) ? 'odd' : 'even';
|
||||
$(this).removeClass().addClass(rowclass);
|
||||
ct++;
|
||||
});
|
||||
// If this is the first item being added, remove
|
||||
// the "No items" message and show the list.
|
||||
if ($('table.data').find('tr').has('td').length > 0 &&
|
||||
$('#nothing2display').is(':visible')
|
||||
) {
|
||||
$('#nothing2display').hide('slow', function () {
|
||||
$('table.data').show('slow');
|
||||
});
|
||||
}
|
||||
Navigation.reload();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
} // end "if (that.validate())"
|
||||
}; // end of function that handles the submission of the Editor
|
||||
that.buttonOptions[Messages.strClose] = function () {
|
||||
$(this).dialog('close');
|
||||
};
|
||||
/**
|
||||
* Display the dialog to the user
|
||||
*/
|
||||
that.$ajaxDialog = $('<div id="rteDialog">' + data.message + '</div>').dialog({
|
||||
width: 700,
|
||||
minWidth: 500,
|
||||
buttons: that.buttonOptions,
|
||||
// Issue #15810 - use button titles for modals (eg: new procedure)
|
||||
// Respect the order: title on href tag, href content, title sent in response
|
||||
title: $this.attr('title') || $this.text() || $(data.title).text(),
|
||||
modal: true,
|
||||
open: function () {
|
||||
$('#rteDialog').dialog('option', 'max-height', $(window).height());
|
||||
if ($('#rteDialog').parents('.ui-dialog').height() > $(window).height()) {
|
||||
$('#rteDialog').dialog('option', 'height', $(window).height());
|
||||
}
|
||||
$(this).find('input[name=item_name]').trigger('focus');
|
||||
$(this).find('input.datefield').each(function () {
|
||||
Functions.addDatepicker($(this).css('width', '95%'), 'date');
|
||||
});
|
||||
$(this).find('input.datetimefield').each(function () {
|
||||
Functions.addDatepicker($(this).css('width', '95%'), 'datetime');
|
||||
});
|
||||
$.datepicker.initialized = false;
|
||||
},
|
||||
close: function () {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
/**
|
||||
* @var mode Used to remember whether the editor is in
|
||||
* "Edit" or "Add" mode
|
||||
*/
|
||||
var mode = 'add';
|
||||
if ($('input[name=editor_process_edit]').length > 0) {
|
||||
mode = 'edit';
|
||||
}
|
||||
// Attach syntax highlighted editor to the definition
|
||||
/**
|
||||
* @var elm jQuery object containing the reference to
|
||||
* the Definition textarea.
|
||||
*/
|
||||
var $elm = $('textarea[name=item_definition]').last();
|
||||
var linterOptions = {};
|
||||
linterOptions.triggerEditor = true;
|
||||
that.syntaxHiglighter = Functions.getSqlEditor($elm, {}, 'both', linterOptions);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.get()
|
||||
},
|
||||
|
||||
dropDialog: function ($this) {
|
||||
/**
|
||||
* @var $curr_row Object containing reference to the current row
|
||||
*/
|
||||
var $currRow = $this.parents('tr');
|
||||
/**
|
||||
* @var question String containing the question to be asked for confirmation
|
||||
*/
|
||||
var question = $('<div></div>').text(
|
||||
$currRow.children('td').children('.drop_sql').html()
|
||||
);
|
||||
// We ask for confirmation first here, before submitting the ajax request
|
||||
$this.confirm(question, $this.attr('href'), function (url) {
|
||||
/**
|
||||
* @var msg jQuery object containing the reference to
|
||||
* the AJAX message shown to the user
|
||||
*/
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strProcessingRequest);
|
||||
var params = Functions.getJsConfirmCommonParam(this, $this.getPostData());
|
||||
$.post(url, params, function (data) {
|
||||
if (data.success === true) {
|
||||
/**
|
||||
* @var $table Object containing reference
|
||||
* to the main list of elements
|
||||
*/
|
||||
var $table = $currRow.parent();
|
||||
// Check how many rows will be left after we remove
|
||||
// the one that the user has requested us to remove
|
||||
if ($table.find('tr').length === 3) {
|
||||
// If there are two rows left, it means that they are
|
||||
// the header of the table and the rows that we are
|
||||
// about to remove, so after the removal there will be
|
||||
// nothing to show in the table, so we hide it.
|
||||
$table.hide('slow', function () {
|
||||
$(this).find('tr.even, tr.odd').remove();
|
||||
$('.withSelected').remove();
|
||||
$('#nothing2display').show('slow');
|
||||
});
|
||||
} else {
|
||||
$currRow.hide('slow', function () {
|
||||
$(this).remove();
|
||||
// Now we have removed the row from the list, but maybe
|
||||
// some row classes are wrong now. So we will iterate
|
||||
// through all rows and assign correct classes to them.
|
||||
/**
|
||||
* @var ct Count of processed rows
|
||||
*/
|
||||
var ct = 0;
|
||||
/**
|
||||
* @var rowclass Class to be attached to the row
|
||||
* that is being processed
|
||||
*/
|
||||
var rowclass = '';
|
||||
$table.find('tr').has('td').each(function () {
|
||||
rowclass = (ct % 2 === 1) ? 'odd' : 'even';
|
||||
$(this).removeClass().addClass(rowclass);
|
||||
ct++;
|
||||
});
|
||||
});
|
||||
}
|
||||
// Get rid of the "Loading" message
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
// Show the query that we just executed
|
||||
Functions.slidingMessage(data.sql_query);
|
||||
Navigation.reload();
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
});
|
||||
},
|
||||
|
||||
dropMultipleDialog: function ($this) {
|
||||
// We ask for confirmation here
|
||||
$this.confirm(Messages.strDropRTEitems, '', function () {
|
||||
/**
|
||||
* @var msg jQuery object containing the reference to
|
||||
* the AJAX message shown to the user
|
||||
*/
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strProcessingRequest);
|
||||
|
||||
// drop anchors of all selected rows
|
||||
var dropAnchors = $('input.checkall:checked').parents('tr').find('.drop_anchor');
|
||||
var success = true;
|
||||
var count = dropAnchors.length;
|
||||
var returnCount = 0;
|
||||
|
||||
dropAnchors.each(function () {
|
||||
var $anchor = $(this);
|
||||
/**
|
||||
* @var $curr_row Object containing reference to the current row
|
||||
*/
|
||||
var $currRow = $anchor.parents('tr');
|
||||
var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData());
|
||||
$.post($anchor.attr('href'), params, function (data) {
|
||||
returnCount++;
|
||||
if (data.success === true) {
|
||||
/**
|
||||
* @var $table Object containing reference
|
||||
* to the main list of elements
|
||||
*/
|
||||
var $table = $currRow.parent();
|
||||
// Check how many rows will be left after we remove
|
||||
// the one that the user has requested us to remove
|
||||
if ($table.find('tr').length === 3) {
|
||||
// If there are two rows left, it means that they are
|
||||
// the header of the table and the rows that we are
|
||||
// about to remove, so after the removal there will be
|
||||
// nothing to show in the table, so we hide it.
|
||||
$table.hide('slow', function () {
|
||||
$(this).find('tr.even, tr.odd').remove();
|
||||
$('.withSelected').remove();
|
||||
$('#nothing2display').show('slow');
|
||||
});
|
||||
} else {
|
||||
$currRow.hide('fast', function () {
|
||||
// we will iterate
|
||||
// through all rows and assign correct classes to them.
|
||||
/**
|
||||
* @var ct Count of processed rows
|
||||
*/
|
||||
var ct = 0;
|
||||
/**
|
||||
* @var rowclass Class to be attached to the row
|
||||
* that is being processed
|
||||
*/
|
||||
var rowclass = '';
|
||||
$table.find('tr').has('td').each(function () {
|
||||
rowclass = (ct % 2 === 1) ? 'odd' : 'even';
|
||||
$(this).removeClass().addClass(rowclass);
|
||||
ct++;
|
||||
});
|
||||
});
|
||||
$currRow.remove();
|
||||
}
|
||||
if (returnCount === count) {
|
||||
if (success) {
|
||||
// Get rid of the "Loading" message
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
$('#rteListForm_checkall').prop({ checked: false, indeterminate: false });
|
||||
}
|
||||
Navigation.reload();
|
||||
}
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
success = false;
|
||||
if (returnCount === count) {
|
||||
Navigation.reload();
|
||||
}
|
||||
}
|
||||
}); // end $.post()
|
||||
}); // end drop_anchors.each()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AJAX.registerOnload('database/triggers.js', function () {
|
||||
/**
|
||||
* Attach Ajax event handlers for the Add/Edit functionality.
|
||||
*/
|
||||
$(document).on('click', 'a.ajax.add_anchor, a.ajax.edit_anchor', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if ($(this).hasClass('add_anchor')) {
|
||||
$.datepicker.initialized = false;
|
||||
}
|
||||
|
||||
DatabaseTriggers.editorDialog($(this).hasClass('add_anchor'), $(this));
|
||||
});
|
||||
|
||||
/**
|
||||
* Attach Ajax event handlers for Export
|
||||
*/
|
||||
$(document).on('click', 'a.ajax.export_anchor', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseTriggers.exportDialog($(this));
|
||||
});
|
||||
|
||||
$(document).on('click', '#bulkActionExportButton', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseTriggers.exportDialog($(this));
|
||||
});
|
||||
|
||||
/**
|
||||
* Attach Ajax event handlers for Drop functionality
|
||||
*/
|
||||
$(document).on('click', 'a.ajax.drop_anchor', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseTriggers.dropDialog($(this));
|
||||
});
|
||||
|
||||
$(document).on('click', '#bulkActionDropButton', function (event) {
|
||||
event.preventDefault();
|
||||
DatabaseTriggers.dropMultipleDialog($(this));
|
||||
});
|
||||
});
|
@ -0,0 +1,178 @@
|
||||
/* global DesignerOfflineDB */ // js/designer/database.js
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
/* global db, selectedPage:writable */ // js/designer/init.js
|
||||
/* global DesignerMove */ // js/designer/move.js
|
||||
/* global DesignerObjects */ // js/designer/objects.js
|
||||
|
||||
var DesignerPage = {};
|
||||
|
||||
DesignerPage.showTablesInLandingPage = function (db) {
|
||||
DesignerPage.loadFirstPage(db, function (page) {
|
||||
if (page) {
|
||||
DesignerPage.loadHtmlForPage(page.pgNr);
|
||||
selectedPage = page.pgNr;
|
||||
} else {
|
||||
DesignerPage.showNewPageTables(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.saveToNewPage = function (db, pageName, tablePositions, callback) {
|
||||
DesignerPage.createNewPage(db, pageName, function (page) {
|
||||
if (page) {
|
||||
var tblCords = [];
|
||||
var saveCallback = function (id) {
|
||||
tblCords.push(id);
|
||||
if (tablePositions.length === tblCords.length) {
|
||||
page.tblCords = tblCords;
|
||||
DesignerOfflineDB.addObject('pdf_pages', page);
|
||||
}
|
||||
};
|
||||
for (var pos = 0; pos < tablePositions.length; pos++) {
|
||||
tablePositions[pos].pdfPgNr = page.pgNr;
|
||||
DesignerPage.saveTablePositions(tablePositions[pos], saveCallback);
|
||||
}
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(page);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.saveToSelectedPage = function (db, pageId, pageName, tablePositions, callback) {
|
||||
DesignerPage.deletePage(pageId);
|
||||
DesignerPage.saveToNewPage(db, pageName, tablePositions, function (page) {
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(page);
|
||||
}
|
||||
selectedPage = page.pgNr;
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.createNewPage = function (db, pageName, callback) {
|
||||
var newPage = new DesignerObjects.PdfPage(db, pageName);
|
||||
DesignerOfflineDB.addObject('pdf_pages', newPage, function (pgNr) {
|
||||
newPage.pgNr = pgNr;
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(newPage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.saveTablePositions = function (positions, callback) {
|
||||
DesignerOfflineDB.addObject('table_coords', positions, callback);
|
||||
};
|
||||
|
||||
DesignerPage.createPageList = function (db, callback) {
|
||||
DesignerOfflineDB.loadAllObjects('pdf_pages', function (pages) {
|
||||
var html = '';
|
||||
for (var p = 0; p < pages.length; p++) {
|
||||
var page = pages[p];
|
||||
if (page.dbName === db) {
|
||||
html += '<option value="' + page.pgNr + '">';
|
||||
html += Functions.escapeHtml(page.pageDescr) + '</option>';
|
||||
}
|
||||
}
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(html);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.deletePage = function (pageId, callback) {
|
||||
DesignerOfflineDB.loadObject('pdf_pages', pageId, function (page) {
|
||||
if (page) {
|
||||
for (var i = 0; i < page.tblCords.length; i++) {
|
||||
DesignerOfflineDB.deleteObject('table_coords', page.tblCords[i]);
|
||||
}
|
||||
DesignerOfflineDB.deleteObject('pdf_pages', pageId, callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.loadFirstPage = function (db, callback) {
|
||||
DesignerOfflineDB.loadAllObjects('pdf_pages', function (pages) {
|
||||
var firstPage = null;
|
||||
for (var i = 0; i < pages.length; i++) {
|
||||
var page = pages[i];
|
||||
if (page.dbName === db) {
|
||||
// give preference to a page having same name as the db
|
||||
if (page.pageDescr === db) {
|
||||
callback(page);
|
||||
return;
|
||||
}
|
||||
if (firstPage === null) {
|
||||
firstPage = page;
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(firstPage);
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.showNewPageTables = function (check) {
|
||||
var allTables = $('#id_scroll_tab').find('td input:checkbox');
|
||||
allTables.prop('checked', check);
|
||||
for (var tab = 0; tab < allTables.length; tab++) {
|
||||
var input = allTables[tab];
|
||||
if (input.value) {
|
||||
var element = document.getElementById(input.value);
|
||||
element.style.top = DesignerPage.getRandom(550, 20) + 'px';
|
||||
element.style.left = DesignerPage.getRandom(700, 20) + 'px';
|
||||
DesignerMove.visibleTab(input, input.value);
|
||||
}
|
||||
}
|
||||
selectedPage = -1;
|
||||
$('#page_name').text(Messages.strUntitled);
|
||||
DesignerMove.markUnsaved();
|
||||
};
|
||||
|
||||
DesignerPage.loadHtmlForPage = function (pageId) {
|
||||
DesignerPage.showNewPageTables(true);
|
||||
DesignerPage.loadPageObjects(pageId, function (page, tblCords) {
|
||||
$('#name-panel').find('#page_name').text(page.pageDescr);
|
||||
var tableMissing = false;
|
||||
for (var t = 0; t < tblCords.length; t++) {
|
||||
var tbId = db + '.' + tblCords[t].tableName;
|
||||
var table = document.getElementById(tbId);
|
||||
if (table === null) {
|
||||
tableMissing = true;
|
||||
continue;
|
||||
}
|
||||
table.style.top = tblCords[t].y + 'px';
|
||||
table.style.left = tblCords[t].x + 'px';
|
||||
|
||||
var checkbox = document.getElementById('check_vis_' + tbId);
|
||||
checkbox.checked = true;
|
||||
DesignerMove.visibleTab(checkbox, checkbox.value);
|
||||
}
|
||||
DesignerMove.markSaved();
|
||||
if (tableMissing === true) {
|
||||
DesignerMove.markUnsaved();
|
||||
Functions.ajaxShowMessage(Messages.strSavedPageTableMissing);
|
||||
}
|
||||
selectedPage = page.pgNr;
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.loadPageObjects = function (pageId, callback) {
|
||||
DesignerOfflineDB.loadObject('pdf_pages', pageId, function (page) {
|
||||
var tblCords = [];
|
||||
var count = page.tblCords.length;
|
||||
for (var i = 0; i < count; i++) {
|
||||
DesignerOfflineDB.loadObject('table_coords', page.tblCords[i], function (tblCord) {
|
||||
tblCords.push(tblCord);
|
||||
if (tblCords.length === count) {
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(page, tblCords);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DesignerPage.getRandom = function (max, min) {
|
||||
var val = Math.random() * (max - min) + min;
|
||||
return Math.floor(val);
|
||||
};
|
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Functions used in the import tab
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Toggles the hiding and showing of each plugin's options
|
||||
* according to the currently selected plugin from the dropdown list
|
||||
*/
|
||||
function changePluginOpts () {
|
||||
$('#format_specific_opts').find('div.format_specific_options').each(function () {
|
||||
$(this).hide();
|
||||
});
|
||||
var selectedPluginName = $('#plugins').find('option:selected').val();
|
||||
$('#' + selectedPluginName + '_options').fadeIn('slow');
|
||||
|
||||
const importNotification = document.getElementById('import_notification');
|
||||
importNotification.innerText = '';
|
||||
if (selectedPluginName === 'csv') {
|
||||
importNotification.innerHTML = '<div class="alert alert-info mb-0 mt-3" role="alert">' + Messages.strImportCSV + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the hiding and showing of each plugin's options and sets the selected value
|
||||
* in the plugin dropdown list according to the format of the selected file
|
||||
*
|
||||
* @param {string} fname
|
||||
*/
|
||||
function matchFile (fname) {
|
||||
var fnameArray = fname.toLowerCase().split('.');
|
||||
var len = fnameArray.length;
|
||||
if (len !== 0) {
|
||||
var extension = fnameArray[len - 1];
|
||||
if (extension === 'gz' || extension === 'bz2' || extension === 'zip') {
|
||||
len--;
|
||||
}
|
||||
// Only toggle if the format of the file can be imported
|
||||
if ($('select[name=\'format\'] option').filterByValue(fnameArray[len - 1]).length === 1) {
|
||||
$('select[name=\'format\'] option').filterByValue(fnameArray[len - 1]).prop('selected', true);
|
||||
changePluginOpts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('import.js', function () {
|
||||
$('#plugins').off('change');
|
||||
$('#input_import_file').off('change');
|
||||
$('#select_local_import_file').off('change');
|
||||
$('#input_import_file').off('change').off('focus');
|
||||
$('#select_local_import_file').off('focus');
|
||||
$('#text_csv_enclosed').add('#text_csv_escaped').off('keyup');
|
||||
});
|
||||
|
||||
AJAX.registerOnload('import.js', function () {
|
||||
// import_file_form validation.
|
||||
$(document).on('submit', '#import_file_form', function () {
|
||||
var radioLocalImport = $('#localFileTab');
|
||||
var radioImport = $('#uploadFileTab');
|
||||
var fileMsg = '<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt="" class="icon ic_s_error"> ' + Messages.strImportDialogMessage + '</div>';
|
||||
var wrongTblNameMsg = '<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt="" class="icon ic_s_error">' + Messages.strTableNameDialogMessage + '</div>';
|
||||
var wrongDBNameMsg = '<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt="" class="icon ic_s_error">' + Messages.strDBNameDialogMessage + '</div>';
|
||||
|
||||
if (radioLocalImport.length !== 0) {
|
||||
// remote upload.
|
||||
|
||||
if (radioImport.hasClass('active') && $('#input_import_file').val() === '') {
|
||||
$('#input_import_file').trigger('focus');
|
||||
Functions.ajaxShowMessage(fileMsg, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (radioLocalImport.hasClass('active')) {
|
||||
if ($('#select_local_import_file').length === 0) {
|
||||
Functions.ajaxShowMessage('<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt="" class="icon ic_s_error"> ' + Messages.strNoImportFile + ' </div>', false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($('#select_local_import_file').val() === '') {
|
||||
$('#select_local_import_file').trigger('focus');
|
||||
Functions.ajaxShowMessage(fileMsg, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// local upload.
|
||||
if ($('#input_import_file').val() === '') {
|
||||
$('#input_import_file').trigger('focus');
|
||||
Functions.ajaxShowMessage(fileMsg, false);
|
||||
return false;
|
||||
}
|
||||
if ($('#text_csv_new_tbl_name').length > 0) {
|
||||
var newTblName = $('#text_csv_new_tbl_name').val();
|
||||
if (newTblName.length > 0 && newTblName.trim().length === 0) {
|
||||
Functions.ajaxShowMessage(wrongTblNameMsg, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($('#text_csv_new_db_name').length > 0) {
|
||||
var newDBName = $('#text_csv_new_db_name').val();
|
||||
if (newDBName.length > 0 && newDBName.trim().length === 0) {
|
||||
Functions.ajaxShowMessage(wrongDBNameMsg, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// show progress bar.
|
||||
$('#upload_form_status').css('display', 'inline');
|
||||
$('#upload_form_status_info').css('display', 'inline');
|
||||
});
|
||||
|
||||
// Initially display the options for the selected plugin
|
||||
changePluginOpts();
|
||||
|
||||
// Whenever the selected plugin changes, change the options displayed
|
||||
$('#plugins').on('change', function () {
|
||||
changePluginOpts();
|
||||
});
|
||||
|
||||
$('#input_import_file').on('change', function () {
|
||||
matchFile($(this).val());
|
||||
});
|
||||
|
||||
$('#select_local_import_file').on('change', function () {
|
||||
matchFile($(this).val());
|
||||
});
|
||||
|
||||
/**
|
||||
* Set up the interface for Javascript-enabled browsers since the default is for
|
||||
* Javascript-disabled browsers
|
||||
*/
|
||||
$('#format_specific_opts').find('div.format_specific_options')
|
||||
.find('h3')
|
||||
.remove();
|
||||
// $("form[name=import] *").unwrap();
|
||||
|
||||
/**
|
||||
* for input element text_csv_enclosed and text_csv_escaped allow just one character to enter.
|
||||
* as mysql allows just one character for these fields,
|
||||
* if first character is escape then allow two including escape character.
|
||||
*/
|
||||
$('#text_csv_enclosed').add('#text_csv_escaped').on('keyup', function () {
|
||||
if ($(this).val().length === 2 && $(this).val().charAt(0) !== '\\') {
|
||||
$(this).val($(this).val().substring(0, 1));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
@ -0,0 +1,819 @@
|
||||
/**
|
||||
* @fileoverview function used for index manipulation pages
|
||||
* @name Table Structure
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @required js/functions.js
|
||||
*/
|
||||
|
||||
/* global fulltextIndexes:writable, indexes:writable, primaryIndexes:writable, spatialIndexes:writable, uniqueIndexes:writable */ // js/functions.js
|
||||
|
||||
var Indexes = {};
|
||||
|
||||
/**
|
||||
* Returns the array of indexes based on the index choice
|
||||
*
|
||||
* @param {string} indexChoice index choice
|
||||
*
|
||||
* @return {null|object}
|
||||
*/
|
||||
Indexes.getIndexArray = function (indexChoice) {
|
||||
var sourceArray = null;
|
||||
|
||||
switch (indexChoice.toLowerCase()) {
|
||||
case 'primary':
|
||||
sourceArray = primaryIndexes;
|
||||
break;
|
||||
case 'unique':
|
||||
sourceArray = uniqueIndexes;
|
||||
break;
|
||||
case 'index':
|
||||
sourceArray = indexes;
|
||||
break;
|
||||
case 'fulltext':
|
||||
sourceArray = fulltextIndexes;
|
||||
break;
|
||||
case 'spatial':
|
||||
sourceArray = spatialIndexes;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return sourceArray;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides/shows the inputs and submits appropriately depending
|
||||
* on whether the index type chosen is 'SPATIAL' or not.
|
||||
*/
|
||||
Indexes.checkIndexType = function () {
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Dropdown to select the index choice.
|
||||
*/
|
||||
var $selectIndexChoice = $('#select_index_choice');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Dropdown to select the index type.
|
||||
*/
|
||||
var $selectIndexType = $('#select_index_type');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Table header for the size column.
|
||||
*/
|
||||
var $sizeHeader = $('#index_columns').find('thead tr').children('th').eq(1);
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Inputs to specify the columns for the index.
|
||||
*/
|
||||
var $columnInputs = $('select[name="index[columns][names][]"]');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Inputs to specify sizes for columns of the index.
|
||||
*/
|
||||
var $sizeInputs = $('input[name="index[columns][sub_parts][]"]');
|
||||
/**
|
||||
* @var {JQuery<HTMLElement}, Footer containing the controllers to add more columns
|
||||
*/
|
||||
var $addMore = $('#index_frm').find('.add_more');
|
||||
|
||||
if ($selectIndexChoice.val() === 'SPATIAL') {
|
||||
// Disable and hide the size column
|
||||
$sizeHeader.hide();
|
||||
$sizeInputs.each(function () {
|
||||
$(this)
|
||||
.prop('disabled', true)
|
||||
.parent('td').hide();
|
||||
});
|
||||
|
||||
// Disable and hide the columns of the index other than the first one
|
||||
var initial = true;
|
||||
$columnInputs.each(function () {
|
||||
var $columnInput = $(this);
|
||||
if (! initial) {
|
||||
$columnInput
|
||||
.prop('disabled', true)
|
||||
.parent('td').hide();
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Hide controllers to add more columns
|
||||
$addMore.hide();
|
||||
} else {
|
||||
// Enable and show the size column
|
||||
$sizeHeader.show();
|
||||
$sizeInputs.each(function () {
|
||||
$(this)
|
||||
.prop('disabled', false)
|
||||
.parent('td').show();
|
||||
});
|
||||
|
||||
// Enable and show the columns of the index
|
||||
$columnInputs.each(function () {
|
||||
$(this)
|
||||
.prop('disabled', false)
|
||||
.parent('td').show();
|
||||
});
|
||||
|
||||
// Show controllers to add more columns
|
||||
$addMore.show();
|
||||
}
|
||||
|
||||
if ($selectIndexChoice.val() === 'SPATIAL' ||
|
||||
$selectIndexChoice.val() === 'FULLTEXT') {
|
||||
$selectIndexType.val('').prop('disabled', true);
|
||||
} else {
|
||||
$selectIndexType.prop('disabled', false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets current index information into form parameters.
|
||||
*
|
||||
* @param {any[]} sourceArray Array containing index columns
|
||||
* @param {string} indexChoice Choice of index
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Indexes.setIndexFormParameters = function (sourceArray, indexChoice) {
|
||||
if (indexChoice === 'index') {
|
||||
$('input[name="indexes"]').val(JSON.stringify(sourceArray));
|
||||
} else {
|
||||
$('input[name="' + indexChoice + '_indexes"]').val(JSON.stringify(sourceArray));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a column from an Index.
|
||||
*
|
||||
* @param {string} colIndex Index of column in form
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Indexes.removeColumnFromIndex = function (colIndex) {
|
||||
// Get previous index details.
|
||||
var previousIndex = $('select[name="field_key[' + colIndex + ']"]')
|
||||
.attr('data-index');
|
||||
if (previousIndex.length) {
|
||||
previousIndex = previousIndex.split(',');
|
||||
var sourceArray = Indexes.getIndexArray(previousIndex[0]);
|
||||
if (sourceArray === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove column from index array.
|
||||
var sourceLength = sourceArray[previousIndex[1]].columns.length;
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
if (sourceArray[previousIndex[1]].columns[i].col_index === colIndex) {
|
||||
sourceArray[previousIndex[1]].columns.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove index completely if no columns left.
|
||||
if (sourceArray[previousIndex[1]].columns.length === 0) {
|
||||
sourceArray.splice(previousIndex[1], 1);
|
||||
}
|
||||
|
||||
// Update current index details.
|
||||
$('select[name="field_key[' + colIndex + ']"]').attr('data-index', '');
|
||||
// Update form index parameters.
|
||||
Indexes.setIndexFormParameters(sourceArray, previousIndex[0].toLowerCase());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a column to an Index.
|
||||
*
|
||||
* @param {any[]} sourceArray Array holding corresponding indexes
|
||||
* @param {string} arrayIndex Index of an INDEX in array
|
||||
* @param {string} indexChoice Choice of Index
|
||||
* @param {string} colIndex Index of column on form
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Indexes.addColumnToIndex = function (sourceArray, arrayIndex, indexChoice, colIndex) {
|
||||
if (colIndex >= 0) {
|
||||
// Remove column from other indexes (if any).
|
||||
Indexes.removeColumnFromIndex(colIndex);
|
||||
}
|
||||
var indexName = $('input[name="index[Key_name]"]').val();
|
||||
var indexComment = $('input[name="index[Index_comment]"]').val();
|
||||
var keyBlockSize = $('input[name="index[Key_block_size]"]').val();
|
||||
var parser = $('input[name="index[Parser]"]').val();
|
||||
var indexType = $('select[name="index[Index_type]"]').val();
|
||||
var columns = [];
|
||||
$('#index_columns').find('tbody').find('tr').each(function () {
|
||||
// Get columns in particular order.
|
||||
var colIndex = $(this).find('select[name="index[columns][names][]"]').val();
|
||||
var size = $(this).find('input[name="index[columns][sub_parts][]"]').val();
|
||||
columns.push({
|
||||
'col_index': colIndex,
|
||||
'size': size
|
||||
});
|
||||
});
|
||||
|
||||
// Update or create an index.
|
||||
sourceArray[arrayIndex] = {
|
||||
'Key_name': indexName,
|
||||
'Index_comment': indexComment,
|
||||
'Index_choice': indexChoice.toUpperCase(),
|
||||
'Key_block_size': keyBlockSize,
|
||||
'Parser': parser,
|
||||
'Index_type': indexType,
|
||||
'columns': columns
|
||||
};
|
||||
|
||||
// Display index name (or column list)
|
||||
var displayName = indexName;
|
||||
if (displayName === '') {
|
||||
var columnNames = [];
|
||||
$.each(columns, function () {
|
||||
columnNames.push($('input[name="field_name[' + this.col_index + ']"]').val());
|
||||
});
|
||||
displayName = '[' + columnNames.join(', ') + ']';
|
||||
}
|
||||
$.each(columns, function () {
|
||||
var id = 'index_name_' + this.col_index + '_8';
|
||||
var $name = $('#' + id);
|
||||
if ($name.length === 0) {
|
||||
$name = $('<a id="' + id + '" href="#" class="ajax show_index_dialog"></a>');
|
||||
$name.insertAfter($('select[name="field_key[' + this.col_index + ']"]'));
|
||||
}
|
||||
var $text = $('<small>').text(displayName);
|
||||
$name.html($text);
|
||||
});
|
||||
|
||||
if (colIndex >= 0) {
|
||||
// Update index details on form.
|
||||
$('select[name="field_key[' + colIndex + ']"]')
|
||||
.attr('data-index', indexChoice + ',' + arrayIndex);
|
||||
}
|
||||
Indexes.setIndexFormParameters(sourceArray, indexChoice.toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
* Get choices list for a column to create a composite index with.
|
||||
*
|
||||
* @param {any[]} sourceArray Array hodling columns for particular index
|
||||
* @param {string} colIndex Choice of index
|
||||
*
|
||||
* @return {JQuery} jQuery Object
|
||||
*/
|
||||
Indexes.getCompositeIndexList = function (sourceArray, colIndex) {
|
||||
// Remove any previous list.
|
||||
if ($('#composite_index_list').length) {
|
||||
$('#composite_index_list').remove();
|
||||
}
|
||||
|
||||
// Html list.
|
||||
var $compositeIndexList = $(
|
||||
'<ul id="composite_index_list">' +
|
||||
'<div>' + Messages.strCompositeWith + '</div>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
||||
// Add each column to list available for composite index.
|
||||
var sourceLength = sourceArray.length;
|
||||
var alreadyPresent = false;
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
var subArrayLen = sourceArray[i].columns.length;
|
||||
var columnNames = [];
|
||||
for (var j = 0; j < subArrayLen; j++) {
|
||||
columnNames.push(
|
||||
$('input[name="field_name[' + sourceArray[i].columns[j].col_index + ']"]').val()
|
||||
);
|
||||
|
||||
if (colIndex === sourceArray[i].columns[j].col_index) {
|
||||
alreadyPresent = true;
|
||||
}
|
||||
}
|
||||
|
||||
$compositeIndexList.append(
|
||||
'<li>' +
|
||||
'<input type="radio" name="composite_with" ' +
|
||||
(alreadyPresent ? 'checked="checked"' : '') +
|
||||
' id="composite_index_' + i + '" value="' + i + '">' +
|
||||
'<label for="composite_index_' + i + '">' + columnNames.join(', ') +
|
||||
'</label>' +
|
||||
'</li>'
|
||||
);
|
||||
}
|
||||
|
||||
return $compositeIndexList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows 'Add Index' dialog.
|
||||
*
|
||||
* @param {any[]} sourceArray Array holding particular index
|
||||
* @param {string} arrayIndex Index of an INDEX in array
|
||||
* @param {any[]} targetColumns Columns for an INDEX
|
||||
* @param {string} colIndex Index of column on form
|
||||
* @param {object} index Index detail object
|
||||
* @param {boolean} showDialog Whether to show index creation dialog or not
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Indexes.showAddIndexDialog = function (sourceArray, arrayIndex, targetColumns, colIndex, index, showDialog) {
|
||||
var showDialogLocal = typeof showDialog !== 'undefined' ? showDialog : true;
|
||||
// Prepare post-data.
|
||||
var $table = $('input[name="table"]');
|
||||
var table = $table.length > 0 ? $table.val() : '';
|
||||
var postData = {
|
||||
'server': CommonParams.get('server'),
|
||||
'db': $('input[name="db"]').val(),
|
||||
'table': table,
|
||||
'ajax_request': 1,
|
||||
'create_edit_table': 1,
|
||||
'index': index
|
||||
};
|
||||
|
||||
var columns = {};
|
||||
for (var i = 0; i < targetColumns.length; i++) {
|
||||
var columnName = $('input[name="field_name[' + targetColumns[i] + ']"]').val();
|
||||
var columnType = $('select[name="field_type[' + targetColumns[i] + ']"]').val().toLowerCase();
|
||||
columns[columnName] = [columnType, targetColumns[i]];
|
||||
}
|
||||
postData.columns = JSON.stringify(columns);
|
||||
|
||||
var buttonOptions = {};
|
||||
buttonOptions[Messages.strGo] = function () {
|
||||
var isMissingValue = false;
|
||||
$('select[name="index[columns][names][]"]').each(function () {
|
||||
if ($(this).val() === '') {
|
||||
isMissingValue = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (! isMissingValue) {
|
||||
Indexes.addColumnToIndex(
|
||||
sourceArray,
|
||||
arrayIndex,
|
||||
index.Index_choice,
|
||||
colIndex
|
||||
);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(
|
||||
'<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt=""' +
|
||||
' class="icon ic_s_error"> ' + Messages.strMissingColumn +
|
||||
' </div>', false
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$(this).remove();
|
||||
};
|
||||
buttonOptions[Messages.strCancel] = function () {
|
||||
if (colIndex >= 0) {
|
||||
// Handle state on 'Cancel'.
|
||||
var $selectList = $('select[name="field_key[' + colIndex + ']"]');
|
||||
if (! $selectList.attr('data-index').length) {
|
||||
$selectList.find('option[value*="none"]').attr('selected', 'selected');
|
||||
} else {
|
||||
var previousIndex = $selectList.attr('data-index').split(',');
|
||||
$selectList.find('option[value*="' + previousIndex[0].toLowerCase() + '"]')
|
||||
.attr('selected', 'selected');
|
||||
}
|
||||
}
|
||||
$(this).dialog('close');
|
||||
};
|
||||
var $msgbox = Functions.ajaxShowMessage();
|
||||
$.post('index.php?route=/table/indexes', postData, function (data) {
|
||||
if (data.success === false) {
|
||||
// in the case of an error, show the error message returned.
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
} else {
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
var $div = $('<div></div>');
|
||||
if (showDialogLocal) {
|
||||
// Show dialog if the request was successful
|
||||
if ($('#addIndex').length > 0) {
|
||||
$('#addIndex').remove();
|
||||
}
|
||||
$div
|
||||
.append(data.message)
|
||||
.dialog({
|
||||
title: Messages.strAddIndex,
|
||||
width: 450,
|
||||
minHeight: 250,
|
||||
create: function () {
|
||||
$(this).on('keypress', function (e) {
|
||||
if (e.which === 13 || e.keyCode === 13 || window.event.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
buttonOptions[Messages.strGo]();
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
open: function () {
|
||||
Functions.checkIndexName('index_frm');
|
||||
Functions.showHints($div);
|
||||
$('#index_columns').find('td').each(function () {
|
||||
$(this).css('width', $(this).width() + 'px');
|
||||
});
|
||||
$('#index_columns').find('tbody').sortable({
|
||||
axis: 'y',
|
||||
containment: $('#index_columns').find('tbody'),
|
||||
tolerance: 'pointer'
|
||||
});
|
||||
},
|
||||
modal: true,
|
||||
buttons: buttonOptions,
|
||||
close: function () {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$div
|
||||
.append(data.message);
|
||||
$div.css({ 'display' : 'none' });
|
||||
$div.appendTo($('body'));
|
||||
$div.attr({ 'id' : 'addIndex' });
|
||||
var isMissingValue = false;
|
||||
$('select[name="index[columns][names][]"]').each(function () {
|
||||
if ($(this).val() === '') {
|
||||
isMissingValue = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (! isMissingValue) {
|
||||
Indexes.addColumnToIndex(
|
||||
sourceArray,
|
||||
arrayIndex,
|
||||
index.Index_choice,
|
||||
colIndex
|
||||
);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(
|
||||
'<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title="" alt=""' +
|
||||
' class="icon ic_s_error"> ' + Messages.strMissingColumn +
|
||||
' </div>', false
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a advanced index type selection dialog.
|
||||
*
|
||||
* @param {any[]} sourceArray Array holding a particular type of indexes
|
||||
* @param {string} indexChoice Choice of index
|
||||
* @param {string} colIndex Index of new column on form
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Indexes.indexTypeSelectionDialog = function (sourceArray, indexChoice, colIndex) {
|
||||
var $singleColumnRadio = $('<input type="radio" id="single_column" name="index_choice"' +
|
||||
' checked="checked">' +
|
||||
'<label for="single_column">' + Messages.strCreateSingleColumnIndex + '</label>');
|
||||
var $compositeIndexRadio = $('<input type="radio" id="composite_index"' +
|
||||
' name="index_choice">' +
|
||||
'<label for="composite_index">' + Messages.strCreateCompositeIndex + '</label>');
|
||||
var $dialogContent = $('<fieldset class="pma-fieldset" id="advance_index_creator"></fieldset>');
|
||||
$dialogContent.append('<legend>' + indexChoice.toUpperCase() + '</legend>');
|
||||
|
||||
|
||||
// For UNIQUE/INDEX type, show choice for single-column and composite index.
|
||||
$dialogContent.append($singleColumnRadio);
|
||||
$dialogContent.append($compositeIndexRadio);
|
||||
|
||||
var buttonOptions = {};
|
||||
// 'OK' operation.
|
||||
buttonOptions[Messages.strGo] = function () {
|
||||
if ($('#single_column').is(':checked')) {
|
||||
var index = {
|
||||
'Key_name': (indexChoice === 'primary' ? 'PRIMARY' : ''),
|
||||
'Index_choice': indexChoice.toUpperCase()
|
||||
};
|
||||
Indexes.showAddIndexDialog(sourceArray, (sourceArray.length), [colIndex], colIndex, index);
|
||||
}
|
||||
|
||||
if ($('#composite_index').is(':checked')) {
|
||||
if ($('input[name="composite_with"]').length !== 0 && $('input[name="composite_with"]:checked').length === 0
|
||||
) {
|
||||
Functions.ajaxShowMessage(
|
||||
'<div class="alert alert-danger" role="alert"><img src="themes/dot.gif" title=""' +
|
||||
' alt="" class="icon ic_s_error"> ' +
|
||||
Messages.strFormEmpty +
|
||||
' </div>',
|
||||
false
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
var arrayIndex = $('input[name="composite_with"]:checked').val();
|
||||
var sourceLength = sourceArray[arrayIndex].columns.length;
|
||||
var targetColumns = [];
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
targetColumns.push(sourceArray[arrayIndex].columns[i].col_index);
|
||||
}
|
||||
targetColumns.push(colIndex);
|
||||
|
||||
Indexes.showAddIndexDialog(sourceArray, arrayIndex, targetColumns, colIndex,
|
||||
sourceArray[arrayIndex]);
|
||||
}
|
||||
|
||||
$(this).remove();
|
||||
};
|
||||
buttonOptions[Messages.strCancel] = function () {
|
||||
// Handle state on 'Cancel'.
|
||||
var $selectList = $('select[name="field_key[' + colIndex + ']"]');
|
||||
if (! $selectList.attr('data-index').length) {
|
||||
$selectList.find('option[value*="none"]').attr('selected', 'selected');
|
||||
} else {
|
||||
var previousIndex = $selectList.attr('data-index').split(',');
|
||||
$selectList.find('option[value*="' + previousIndex[0].toLowerCase() + '"]')
|
||||
.attr('selected', 'selected');
|
||||
}
|
||||
$(this).remove();
|
||||
};
|
||||
$('<div></div>').append($dialogContent).dialog({
|
||||
minWidth: 525,
|
||||
minHeight: 200,
|
||||
modal: true,
|
||||
title: Messages.strAddIndex,
|
||||
resizable: false,
|
||||
buttons: buttonOptions,
|
||||
open: function () {
|
||||
$('#composite_index').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$dialogContent.append(Indexes.getCompositeIndexList(sourceArray, colIndex));
|
||||
}
|
||||
});
|
||||
$('#single_column').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
if ($('#composite_index_list').length) {
|
||||
$('#composite_index_list').remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
close: function () {
|
||||
$('#composite_index').off('change');
|
||||
$('#single_column').off('change');
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('indexes.js', function () {
|
||||
$(document).off('click', '#save_index_frm');
|
||||
$(document).off('click', '#preview_index_frm');
|
||||
$(document).off('change', '#select_index_choice');
|
||||
$(document).off('click', 'a.drop_primary_key_index_anchor.ajax');
|
||||
$(document).off('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax');
|
||||
$(document).off('click', '#table_index tbody tr td.rename_index.ajax');
|
||||
$(document).off('click', '#index_frm input[type=submit]');
|
||||
$('body').off('change', 'select[name*="field_key"]');
|
||||
$(document).off('click', '.show_index_dialog');
|
||||
});
|
||||
|
||||
/**
|
||||
* @description <p>Ajax scripts for table index page</p>
|
||||
*
|
||||
* Actions ajaxified here:
|
||||
* <ul>
|
||||
* <li>Showing/hiding inputs depending on the index type chosen</li>
|
||||
* <li>create/edit/drop indexes</li>
|
||||
* </ul>
|
||||
*/
|
||||
AJAX.registerOnload('indexes.js', function () {
|
||||
// Re-initialize variables.
|
||||
primaryIndexes = [];
|
||||
uniqueIndexes = [];
|
||||
indexes = [];
|
||||
fulltextIndexes = [];
|
||||
spatialIndexes = [];
|
||||
|
||||
// for table creation form
|
||||
var $engineSelector = $('.create_table_form select[name=tbl_storage_engine]');
|
||||
if ($engineSelector.length) {
|
||||
Functions.hideShowConnection($engineSelector);
|
||||
}
|
||||
|
||||
var $form = $('#index_frm');
|
||||
if ($form.length > 0) {
|
||||
Functions.showIndexEditDialog($form);
|
||||
}
|
||||
|
||||
$(document).on('click', '#save_index_frm', function (event) {
|
||||
event.preventDefault();
|
||||
var $form = $('#index_frm');
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var submitData = $form.serialize() + argsep + 'do_save_data=1' + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true';
|
||||
Functions.ajaxShowMessage(Messages.strProcessingRequest);
|
||||
AJAX.source = $form;
|
||||
$.post($form.attr('action'), submitData, AJAX.responseHandler);
|
||||
});
|
||||
|
||||
$(document).on('click', '#preview_index_frm', function (event) {
|
||||
event.preventDefault();
|
||||
Functions.previewSql($('#index_frm'));
|
||||
});
|
||||
|
||||
$(document).on('change', '#select_index_choice', function (event) {
|
||||
event.preventDefault();
|
||||
Indexes.checkIndexType();
|
||||
Functions.checkIndexName('index_frm');
|
||||
});
|
||||
|
||||
/**
|
||||
* Ajax Event handler for 'Drop Index'
|
||||
*/
|
||||
$(document).on('click', 'a.drop_primary_key_index_anchor.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var $anchor = $(this);
|
||||
/**
|
||||
* @var $currRow Object containing reference to the current field's row
|
||||
*/
|
||||
var $currRow = $anchor.parents('tr');
|
||||
/** @var {number} rows Number of columns in the key */
|
||||
var rows = $anchor.parents('td').attr('rowspan') || 1;
|
||||
/** @var {number} $rowsToHide Rows that should be hidden */
|
||||
var $rowsToHide = $currRow;
|
||||
for (var i = 1, $lastRow = $currRow.next(); i < rows; i++, $lastRow = $lastRow.next()) {
|
||||
$rowsToHide = $rowsToHide.add($lastRow);
|
||||
}
|
||||
|
||||
var question = $currRow.children('td')
|
||||
.children('.drop_primary_key_index_msg')
|
||||
.val();
|
||||
|
||||
Functions.confirmPreviewSql(question, $anchor.attr('href'), function (url) {
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strDroppingPrimaryKeyIndex, false);
|
||||
var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData());
|
||||
$.post(url, params, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
var $tableRef = $rowsToHide.closest('table');
|
||||
if ($rowsToHide.length === $tableRef.find('tbody > tr').length) {
|
||||
// We are about to remove all rows from the table
|
||||
$tableRef.hide('medium', function () {
|
||||
$('div.no_indexes_defined').show('medium');
|
||||
$rowsToHide.remove();
|
||||
});
|
||||
$tableRef.siblings('.alert-primary').hide('medium');
|
||||
} else {
|
||||
// We are removing some of the rows only
|
||||
$rowsToHide.hide('medium', function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
if ($('.result_query').length) {
|
||||
$('.result_query').remove();
|
||||
}
|
||||
if (data.sql_query) {
|
||||
$('<div class="result_query"></div>')
|
||||
.html(data.sql_query)
|
||||
.prependTo('#structure_content');
|
||||
Functions.highlightSql($('#page_content'));
|
||||
}
|
||||
Navigation.reload();
|
||||
CommonActions.refreshMain('index.php?route=/table/structure');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(Messages.strErrorProcessingRequest + ' : ' + data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
});
|
||||
}); // end Drop Primary Key/Index
|
||||
|
||||
/**
|
||||
* Ajax event handler for index edit
|
||||
**/
|
||||
$(document).on('click', '#table_index tbody tr td.edit_index.ajax, #index_div .add_index.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var url;
|
||||
var title;
|
||||
if ($(this).find('a').length === 0) {
|
||||
// Add index
|
||||
var valid = Functions.checkFormElementInRange(
|
||||
$(this).closest('form')[0],
|
||||
'added_fields',
|
||||
'Column count has to be larger than zero.'
|
||||
);
|
||||
if (! valid) {
|
||||
return;
|
||||
}
|
||||
url = $(this).closest('form').serialize();
|
||||
title = Messages.strAddIndex;
|
||||
} else {
|
||||
// Edit index
|
||||
url = $(this).find('a').getPostData();
|
||||
title = Messages.strEditIndex;
|
||||
}
|
||||
url += CommonParams.get('arg_separator') + 'ajax_request=true';
|
||||
Functions.indexEditorDialog(url, title, function (data) {
|
||||
CommonParams.set('db', data.params.db);
|
||||
CommonParams.set('table', data.params.table);
|
||||
CommonActions.refreshMain('index.php?route=/table/structure');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Ajax event handler for index rename
|
||||
**/
|
||||
$(document).on('click', '#table_index tbody tr td.rename_index.ajax', function (event) {
|
||||
event.preventDefault();
|
||||
var url = $(this).find('a').getPostData();
|
||||
var title = Messages.strRenameIndex;
|
||||
url += CommonParams.get('arg_separator') + 'ajax_request=true';
|
||||
Functions.indexRenameDialog(url, title, function (data) {
|
||||
CommonParams.set('db', data.params.db);
|
||||
CommonParams.set('table', data.params.table);
|
||||
CommonActions.refreshMain('index.php?route=/table/structure');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Ajax event handler for advanced index creation during table creation
|
||||
* and column addition.
|
||||
*/
|
||||
$('body').on('change', 'select[name*="field_key"]', function (e, showDialog) {
|
||||
var showDialogLocal = typeof showDialog !== 'undefined' ? showDialog : true;
|
||||
// Index of column on Table edit and create page.
|
||||
var colIndex = /\d+/.exec($(this).attr('name'));
|
||||
colIndex = colIndex[0];
|
||||
// Choice of selected index.
|
||||
var indexChoice = /[a-z]+/.exec($(this).val());
|
||||
indexChoice = indexChoice[0];
|
||||
// Array containing corresponding indexes.
|
||||
var sourceArray = null;
|
||||
|
||||
if (indexChoice === 'none') {
|
||||
Indexes.removeColumnFromIndex(colIndex);
|
||||
var id = 'index_name_' + '0' + '_8';
|
||||
var $name = $('#' + id);
|
||||
if ($name.length === 0) {
|
||||
$name = $('<a id="' + id + '" href="#" class="ajax show_index_dialog"></a>');
|
||||
$name.insertAfter($('select[name="field_key[' + '0' + ']"]'));
|
||||
}
|
||||
$name.html('');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select a source array.
|
||||
sourceArray = Indexes.getIndexArray(indexChoice);
|
||||
if (sourceArray === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceArray.length === 0) {
|
||||
var index = {
|
||||
'Key_name': (indexChoice === 'primary' ? 'PRIMARY' : ''),
|
||||
'Index_choice': indexChoice.toUpperCase()
|
||||
};
|
||||
Indexes.showAddIndexDialog(sourceArray, 0, [colIndex], colIndex, index, showDialogLocal);
|
||||
} else {
|
||||
if (indexChoice === 'primary') {
|
||||
var arrayIndex = 0;
|
||||
var sourceLength = sourceArray[arrayIndex].columns.length;
|
||||
var targetColumns = [];
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
targetColumns.push(sourceArray[arrayIndex].columns[i].col_index);
|
||||
}
|
||||
targetColumns.push(colIndex);
|
||||
Indexes.showAddIndexDialog(sourceArray, arrayIndex, targetColumns, colIndex,
|
||||
sourceArray[arrayIndex], showDialogLocal);
|
||||
} else {
|
||||
// If there are multiple columns selected for an index, show advanced dialog.
|
||||
Indexes.indexTypeSelectionDialog(sourceArray, indexChoice, colIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.show_index_dialog', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get index details.
|
||||
var previousIndex = $(this).prev('select')
|
||||
.attr('data-index')
|
||||
.split(',');
|
||||
|
||||
var indexChoice = previousIndex[0];
|
||||
var arrayIndex = previousIndex[1];
|
||||
|
||||
var sourceArray = Indexes.getIndexArray(indexChoice);
|
||||
if (sourceArray !== null) {
|
||||
var sourceLength = sourceArray[arrayIndex].columns.length;
|
||||
|
||||
var targetColumns = [];
|
||||
for (var i = 0; i < sourceLength; i++) {
|
||||
targetColumns.push(sourceArray[arrayIndex].columns[i].col_index);
|
||||
}
|
||||
|
||||
Indexes.showAddIndexDialog(sourceArray, arrayIndex, targetColumns, -1, sourceArray[arrayIndex]);
|
||||
}
|
||||
});
|
||||
|
||||
$('#index_frm').on('submit', function () {
|
||||
if (typeof(this.elements['index[Key_name]'].disabled) !== 'undefined') {
|
||||
this.elements['index[Key_name]'].disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,792 @@
|
||||
/**
|
||||
* @fileoverview events handling from normalization page
|
||||
* @name normalization
|
||||
*
|
||||
* @requires jQuery
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
/* global centralColumnList:writable */ // js/functions.js
|
||||
|
||||
/**
|
||||
* AJAX scripts for normalization
|
||||
*
|
||||
*/
|
||||
|
||||
var normalizeto = '1nf';
|
||||
var primaryKey;
|
||||
var dataParsed = null;
|
||||
|
||||
function appendHtmlColumnsList () {
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'getColumns': true
|
||||
},
|
||||
function (data) {
|
||||
if (data.success === true) {
|
||||
$('select[name=makeAtomic]').html(data.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function goTo3NFStep1 (newTables) {
|
||||
var tables = newTables;
|
||||
if (Object.keys(tables).length === 1) {
|
||||
tables = [CommonParams.get('table')];
|
||||
}
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'server': CommonParams.get('server'),
|
||||
'tables': tables,
|
||||
'step': '3.1'
|
||||
}, function (data) {
|
||||
$('#page_content').find('h3').html(Messages.str3NFNormalization);
|
||||
$('#mainContent').find('legend').html(data.legendText);
|
||||
$('#mainContent').find('h4').html(data.headText);
|
||||
$('#mainContent').find('p').html(data.subText);
|
||||
$('#mainContent').find('#extra').html(data.extra);
|
||||
$('#extra').find('form').each(function () {
|
||||
var formId = $(this).attr('id');
|
||||
var colName = $(this).data('colname');
|
||||
$('#' + formId + ' input[value=\'' + colName + '\']').next().remove();
|
||||
$('#' + formId + ' input[value=\'' + colName + '\']').remove();
|
||||
});
|
||||
$('#mainContent').find('#newCols').html('');
|
||||
$('.tblFooters').html('');
|
||||
|
||||
if (data.subText !== '') {
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'button',
|
||||
value: Messages.strDone,
|
||||
class: 'btn btn-primary'
|
||||
})
|
||||
.on('click', function () {
|
||||
processDependencies('', true);
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function goTo2NFStep1 () {
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'step': '2.1'
|
||||
}, function (data) {
|
||||
$('#page_content h3').html(Messages.str2NFNormalization);
|
||||
$('#mainContent legend').html(data.legendText);
|
||||
$('#mainContent h4').html(data.headText);
|
||||
$('#mainContent p').html(data.subText);
|
||||
$('#mainContent #extra').html(data.extra);
|
||||
$('#mainContent #newCols').html('');
|
||||
if (data.subText !== '') {
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'submit',
|
||||
value: Messages.strDone,
|
||||
class: 'btn btn-primary'
|
||||
})
|
||||
.on('click', function () {
|
||||
processDependencies(data.primary_key);
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
} else {
|
||||
if (normalizeto === '3nf') {
|
||||
$('#mainContent #newCols').html(Messages.strToNextStep);
|
||||
setTimeout(function () {
|
||||
goTo3NFStep1([CommonParams.get('table')]);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function goToFinish1NF () {
|
||||
if (normalizeto !== '1nf') {
|
||||
goTo2NFStep1();
|
||||
return true;
|
||||
}
|
||||
$('#mainContent legend').html(Messages.strEndStep);
|
||||
$('#mainContent h4').html(
|
||||
'<h3>' + Functions.sprintf(Messages.strFinishMsg, Functions.escapeHtml(CommonParams.get('table'))) + '</h3>'
|
||||
);
|
||||
$('#mainContent p').html('');
|
||||
$('#mainContent #extra').html('');
|
||||
$('#mainContent #newCols').html('');
|
||||
$('.tblFooters').html('');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function goToStep4 () {
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'step4': true
|
||||
}, function (data) {
|
||||
$('#mainContent legend').html(data.legendText);
|
||||
$('#mainContent h4').html(data.headText);
|
||||
$('#mainContent p').html(data.subText);
|
||||
$('#mainContent #extra').html(data.extra);
|
||||
$('#mainContent #newCols').html('');
|
||||
$('.tblFooters').html('');
|
||||
for (var pk in primaryKey) {
|
||||
$('#extra input[value=\'' + Functions.escapeJsString(primaryKey[pk]) + '\']').attr('disabled','disabled');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function goToStep3 () {
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'step3': true
|
||||
}, function (data) {
|
||||
$('#mainContent legend').html(data.legendText);
|
||||
$('#mainContent h4').html(data.headText);
|
||||
$('#mainContent p').html(data.subText);
|
||||
$('#mainContent #extra').html(data.extra);
|
||||
$('#mainContent #newCols').html('');
|
||||
$('.tblFooters').html('');
|
||||
primaryKey = JSON.parse(data.primary_key);
|
||||
for (var pk in primaryKey) {
|
||||
$('#extra input[value=\'' + Functions.escapeJsString(primaryKey[pk]) + '\']').attr('disabled','disabled');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function goToStep2 (extra) {
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'step2': true
|
||||
}, function (data) {
|
||||
$('#mainContent legend').html(data.legendText);
|
||||
$('#mainContent h4').html(data.headText);
|
||||
$('#mainContent p').html(data.subText);
|
||||
$('#mainContent #extra,#mainContent #newCols').html('');
|
||||
$('.tblFooters').html('');
|
||||
if (data.hasPrimaryKey === '1') {
|
||||
if (extra === 'goToStep3') {
|
||||
$('#mainContent h4').html(Messages.strPrimaryKeyAdded);
|
||||
$('#mainContent p').html(Messages.strToNextStep);
|
||||
}
|
||||
if (extra === 'goToFinish1NF') {
|
||||
goToFinish1NF();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
goToStep3();
|
||||
}, 3000);
|
||||
}
|
||||
} else {
|
||||
// form to select columns to make primary
|
||||
$('#mainContent #extra').html(data.extra);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function goTo2NFFinish (pd) {
|
||||
var tables = {};
|
||||
for (var dependson in pd) {
|
||||
tables[dependson] = $('#extra input[name="' + dependson + '"]').val();
|
||||
}
|
||||
var datastring = {
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'pd': JSON.stringify(pd),
|
||||
'newTablesName':JSON.stringify(tables),
|
||||
'createNewTables2NF':1 };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?route=/normalization',
|
||||
data: datastring,
|
||||
async:false,
|
||||
success: function (data) {
|
||||
if (data.success === true) {
|
||||
if (data.queryError === false) {
|
||||
if (normalizeto === '3nf') {
|
||||
$('#pma_navigation_reload').trigger('click');
|
||||
goTo3NFStep1(tables);
|
||||
return true;
|
||||
}
|
||||
$('#mainContent legend').html(data.legendText);
|
||||
$('#mainContent h4').html(data.headText);
|
||||
$('#mainContent p').html('');
|
||||
$('#mainContent #extra').html('');
|
||||
$('.tblFooters').html('');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.extra, false);
|
||||
}
|
||||
$('#pma_navigation_reload').trigger('click');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function goTo3NFFinish (newTables) {
|
||||
for (var table in newTables) {
|
||||
for (var newtbl in newTables[table]) {
|
||||
var updatedname = $('#extra input[name="' + newtbl + '"]').val();
|
||||
newTables[table][updatedname] = newTables[table][newtbl];
|
||||
if (updatedname !== newtbl) {
|
||||
delete newTables[table][newtbl];
|
||||
}
|
||||
}
|
||||
}
|
||||
var datastring = {
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'server': CommonParams.get('server'),
|
||||
'newTables':JSON.stringify(newTables),
|
||||
'createNewTables3NF':1 };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?route=/normalization',
|
||||
data: datastring,
|
||||
async:false,
|
||||
success: function (data) {
|
||||
if (data.success === true) {
|
||||
if (data.queryError === false) {
|
||||
$('#mainContent legend').html(data.legendText);
|
||||
$('#mainContent h4').html(data.headText);
|
||||
$('#mainContent p').html('');
|
||||
$('#mainContent #extra').html('');
|
||||
$('.tblFooters').html('');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.extra, false);
|
||||
}
|
||||
$('#pma_navigation_reload').trigger('click');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var backup = '';
|
||||
|
||||
function goTo2NFStep2 (pd, primaryKey) {
|
||||
$('#newCols').html('');
|
||||
$('#mainContent legend').html(Messages.strStep + ' 2.2 ' + Messages.strConfirmPd);
|
||||
$('#mainContent h4').html(Messages.strSelectedPd);
|
||||
$('#mainContent p').html(Messages.strPdHintNote);
|
||||
var extra = '<div class="dependencies_box">';
|
||||
var pdFound = false;
|
||||
for (var dependson in pd) {
|
||||
if (dependson !== primaryKey) {
|
||||
pdFound = true;
|
||||
extra += '<p class="d-block m-1">' + Functions.escapeHtml(dependson) + ' -> ' + Functions.escapeHtml(pd[dependson].toString()) + '</p>';
|
||||
}
|
||||
}
|
||||
if (!pdFound) {
|
||||
extra += '<p class="d-block m-1">' + Messages.strNoPdSelected + '</p>';
|
||||
extra += '</div>';
|
||||
} else {
|
||||
extra += '</div>';
|
||||
var datastring = {
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'pd': JSON.stringify(pd),
|
||||
'getNewTables2NF':1 };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?route=/normalization',
|
||||
data: datastring,
|
||||
async:false,
|
||||
success: function (data) {
|
||||
if (data.success === true) {
|
||||
extra += data.message;
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
$('#mainContent #extra').html(extra);
|
||||
$('.tblFooters').html('<input type="button" class="btn btn-primary" value="' + Messages.strBack + '" id="backEditPd"><input type="button" class="btn btn-primary" id="goTo2NFFinish" value="' + Messages.strGo + '">');
|
||||
$('#goTo2NFFinish').on('click', function () {
|
||||
goTo2NFFinish(pd);
|
||||
});
|
||||
}
|
||||
|
||||
function goTo3NFStep2 (pd, tablesTds) {
|
||||
$('#newCols').html('');
|
||||
$('#mainContent legend').html(Messages.strStep + ' 3.2 ' + Messages.strConfirmTd);
|
||||
$('#mainContent h4').html(Messages.strSelectedTd);
|
||||
$('#mainContent p').html(Messages.strPdHintNote);
|
||||
var extra = '<div class="dependencies_box">';
|
||||
var pdFound = false;
|
||||
for (var table in tablesTds) {
|
||||
for (var i in tablesTds[table]) {
|
||||
var dependson = tablesTds[table][i];
|
||||
if (dependson !== '' && dependson !== table) {
|
||||
pdFound = true;
|
||||
extra += '<p class="d-block m-1">' + Functions.escapeHtml(dependson) + ' -> ' + Functions.escapeHtml(pd[dependson].toString()) + '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pdFound) {
|
||||
extra += '<p class="d-block m-1">' + Messages.strNoTdSelected + '</p>';
|
||||
extra += '</div>';
|
||||
} else {
|
||||
extra += '</div>';
|
||||
var datastring = {
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'tables': JSON.stringify(tablesTds),
|
||||
'server': CommonParams.get('server'),
|
||||
'pd': JSON.stringify(pd),
|
||||
'getNewTables3NF':1 };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?route=/normalization',
|
||||
data: datastring,
|
||||
async:false,
|
||||
success: function (data) {
|
||||
dataParsed = data;
|
||||
if (data.success === true) {
|
||||
extra += dataParsed.html;
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
$('#mainContent #extra').html(extra);
|
||||
$('.tblFooters').html('<input type="button" class="btn btn-primary" value="' + Messages.strBack + '" id="backEditPd"><input type="button" class="btn btn-primary" id="goTo3NFFinish" value="' + Messages.strGo + '">');
|
||||
$('#goTo3NFFinish').on('click', function () {
|
||||
if (!pdFound) {
|
||||
goTo3NFFinish([]);
|
||||
} else {
|
||||
goTo3NFFinish(dataParsed.newTables);
|
||||
}
|
||||
});
|
||||
}
|
||||
function processDependencies (primaryKey, isTransitive) {
|
||||
var pk = primaryKey;
|
||||
var pd = {};
|
||||
var tablesTds = {};
|
||||
var dependsOn;
|
||||
pd[pk] = [];
|
||||
$('#extra form').each(function () {
|
||||
var tblname;
|
||||
if (isTransitive === true) {
|
||||
tblname = $(this).data('tablename');
|
||||
pk = tblname;
|
||||
if (!(tblname in tablesTds)) {
|
||||
tablesTds[tblname] = [];
|
||||
}
|
||||
tablesTds[tblname].push(pk);
|
||||
}
|
||||
var formId = $(this).attr('id');
|
||||
$('#' + formId + ' input[type=checkbox]:not(:checked)').prop('checked', false);
|
||||
dependsOn = '';
|
||||
$('#' + formId + ' input[type=checkbox]:checked').each(function () {
|
||||
dependsOn += $(this).val() + ', ';
|
||||
$(this).attr('checked','checked');
|
||||
});
|
||||
if (dependsOn === '') {
|
||||
dependsOn = pk;
|
||||
} else {
|
||||
dependsOn = dependsOn.slice(0, -2);
|
||||
}
|
||||
if (! (dependsOn in pd)) {
|
||||
pd[dependsOn] = [];
|
||||
}
|
||||
pd[dependsOn].push($(this).data('colname'));
|
||||
if (isTransitive === true) {
|
||||
if (!(tblname in tablesTds)) {
|
||||
tablesTds[tblname] = [];
|
||||
}
|
||||
if ($.inArray(dependsOn, tablesTds[tblname]) === -1) {
|
||||
tablesTds[tblname].push(dependsOn);
|
||||
}
|
||||
}
|
||||
});
|
||||
backup = $('#mainContent').html();
|
||||
if (isTransitive === true) {
|
||||
goTo3NFStep2(pd, tablesTds);
|
||||
} else {
|
||||
goTo2NFStep2(pd, pk);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function moveRepeatingGroup (repeatingCols) {
|
||||
var newTable = $('input[name=repeatGroupTable]').val();
|
||||
var newColumn = $('input[name=repeatGroupColumn]').val();
|
||||
if (!newTable) {
|
||||
$('input[name=repeatGroupTable]').trigger('focus');
|
||||
return false;
|
||||
}
|
||||
if (!newColumn) {
|
||||
$('input[name=repeatGroupColumn]').trigger('focus');
|
||||
return false;
|
||||
}
|
||||
var datastring = {
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'repeatingColumns': repeatingCols,
|
||||
'newTable':newTable,
|
||||
'newColumn':newColumn,
|
||||
'primary_columns':primaryKey.toString()
|
||||
};
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?route=/normalization',
|
||||
data: datastring,
|
||||
async:false,
|
||||
success: function (data) {
|
||||
if (data.success === true) {
|
||||
if (data.queryError === false) {
|
||||
goToStep3();
|
||||
}
|
||||
Functions.ajaxShowMessage(data.message, false);
|
||||
$('#pma_navigation_reload').trigger('click');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
AJAX.registerTeardown('normalization.js', function () {
|
||||
$('#extra').off('click', '#selectNonAtomicCol');
|
||||
$('#splitGo').off('click');
|
||||
$('.tblFooters').off('click', '#saveSplit');
|
||||
$('#extra').off('click', '#addNewPrimary');
|
||||
$('.tblFooters').off('click', '#saveNewPrimary');
|
||||
$('#extra').off('click', '#removeRedundant');
|
||||
$('#mainContent p').off('click', '#createPrimaryKey');
|
||||
$('#mainContent').off('click', '#backEditPd');
|
||||
$('#mainContent').off('click', '#showPossiblePd');
|
||||
$('#mainContent').off('click', '.pickPd');
|
||||
});
|
||||
|
||||
AJAX.registerOnload('normalization.js', function () {
|
||||
var selectedCol;
|
||||
normalizeto = $('#mainContent').data('normalizeto');
|
||||
$('#extra').on('click', '#selectNonAtomicCol', function () {
|
||||
if ($(this).val() === 'no_such_col') {
|
||||
goToStep2();
|
||||
} else {
|
||||
selectedCol = $(this).val();
|
||||
}
|
||||
});
|
||||
|
||||
$('#splitGo').on('click', function () {
|
||||
if (!selectedCol || selectedCol === '') {
|
||||
return false;
|
||||
}
|
||||
var numField = $('#numField').val();
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'splitColumn': true,
|
||||
'numFields': numField
|
||||
},
|
||||
function (data) {
|
||||
if (data.success === true) {
|
||||
$('#newCols').html(data.message);
|
||||
$('.default_value').hide();
|
||||
$('.enum_notice').hide();
|
||||
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'submit',
|
||||
id: 'saveSplit',
|
||||
value: Messages.strSave,
|
||||
class: 'btn btn-primary'
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'submit',
|
||||
id: 'cancelSplit',
|
||||
value: Messages.strCancel,
|
||||
class: 'btn btn-secondary'
|
||||
})
|
||||
.on('click', function () {
|
||||
$('#newCols').html('');
|
||||
$(this).parent().html('');
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
}
|
||||
}
|
||||
);
|
||||
return false;
|
||||
});
|
||||
$('.tblFooters').on('click','#saveSplit', function () {
|
||||
centralColumnList = [];
|
||||
if ($('#newCols #field_0_1').val() === '') {
|
||||
$('#newCols #field_0_1').trigger('focus');
|
||||
return false;
|
||||
}
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var datastring = $('#newCols :input').serialize();
|
||||
datastring += argsep + 'ajax_request=1' + argsep + 'do_save_data=1' + argsep + 'field_where=last';
|
||||
$.post('index.php?route=/table/add-field', datastring, function (data) {
|
||||
if (data.success) {
|
||||
$.post(
|
||||
'index.php?route=/sql',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'dropped_column': selectedCol,
|
||||
'purge' : 1,
|
||||
'sql_query': 'ALTER TABLE `' + CommonParams.get('table') + '` DROP `' + selectedCol + '`;',
|
||||
'is_js_confirmed': 1
|
||||
},
|
||||
function (data) {
|
||||
if (data.success === true) {
|
||||
appendHtmlColumnsList();
|
||||
$('#newCols').html('');
|
||||
$('.tblFooters').html('');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
selectedCol = '';
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#extra').on('click', '#addNewPrimary', function () {
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'addNewPrimary': true
|
||||
},
|
||||
function (data) {
|
||||
if (data.success === true) {
|
||||
$('#newCols').html(data.message);
|
||||
$('.default_value').hide();
|
||||
$('.enum_notice').hide();
|
||||
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'submit',
|
||||
id: 'saveNewPrimary',
|
||||
value: Messages.strSave,
|
||||
class: 'btn btn-primary'
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'submit',
|
||||
id: 'cancelSplit',
|
||||
value: Messages.strCancel,
|
||||
class: 'btn btn-secondary'
|
||||
})
|
||||
.on('click', function () {
|
||||
$('#newCols').html('');
|
||||
$(this).parent().html('');
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
);
|
||||
return false;
|
||||
});
|
||||
$('.tblFooters').on('click', '#saveNewPrimary', function () {
|
||||
var datastring = $('#newCols :input').serialize();
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
datastring += argsep + 'field_key[0]=primary_0' + argsep + 'ajax_request=1' + argsep + 'do_save_data=1' + argsep + 'field_where=last';
|
||||
$.post('index.php?route=/table/add-field', datastring, function (data) {
|
||||
if (data.success === true) {
|
||||
$('#mainContent h4').html(Messages.strPrimaryKeyAdded);
|
||||
$('#mainContent p').html(Messages.strToNextStep);
|
||||
$('#mainContent #extra').html('');
|
||||
$('#mainContent #newCols').html('');
|
||||
$('.tblFooters').html('');
|
||||
setTimeout(function () {
|
||||
goToStep3();
|
||||
}, 2000);
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#extra').on('click', '#removeRedundant', function () {
|
||||
var dropQuery = 'ALTER TABLE `' + CommonParams.get('table') + '` ';
|
||||
$('#extra input[type=checkbox]:checked').each(function () {
|
||||
dropQuery += 'DROP `' + $(this).val() + '`, ';
|
||||
});
|
||||
dropQuery = dropQuery.slice(0, -2);
|
||||
$.post(
|
||||
'index.php?route=/sql',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'sql_query': dropQuery,
|
||||
'is_js_confirmed': 1
|
||||
},
|
||||
function (data) {
|
||||
if (data.success === true) {
|
||||
goToStep2('goToFinish1NF');
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
$('#extra').on('click', '#moveRepeatingGroup', function () {
|
||||
var repeatingCols = '';
|
||||
$('#extra input[type=checkbox]:checked').each(function () {
|
||||
repeatingCols += $(this).val() + ', ';
|
||||
});
|
||||
|
||||
if (repeatingCols !== '') {
|
||||
var newColName = $('#extra input[type=checkbox]:checked').first().val();
|
||||
repeatingCols = repeatingCols.slice(0, -2);
|
||||
var confirmStr = Functions.sprintf(Messages.strMoveRepeatingGroup, Functions.escapeHtml(repeatingCols), Functions.escapeHtml(CommonParams.get('table')));
|
||||
confirmStr += '<input type="text" name="repeatGroupTable" placeholder="' + Messages.strNewTablePlaceholder + '">' +
|
||||
'( ' + Functions.escapeHtml(primaryKey.toString()) + ', <input type="text" name="repeatGroupColumn" placeholder="' + Messages.strNewColumnPlaceholder + '" value="' + Functions.escapeHtml(newColName) + '">)' +
|
||||
'</ol>';
|
||||
$('#newCols').html(confirmStr);
|
||||
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'submit',
|
||||
value: Messages.strCancel,
|
||||
class: 'btn btn-secondary'
|
||||
})
|
||||
.on('click', function () {
|
||||
$('#newCols').html('');
|
||||
$('#extra input[type=checkbox]').prop('checked', false);
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'submit',
|
||||
value: Messages.strGo,
|
||||
class: 'btn btn-primary'
|
||||
})
|
||||
.on('click', function () {
|
||||
moveRepeatingGroup(repeatingCols);
|
||||
})
|
||||
.appendTo('.tblFooters');
|
||||
}
|
||||
});
|
||||
$('#mainContent p').on('click', '#createPrimaryKey', function (event) {
|
||||
event.preventDefault();
|
||||
var url = {
|
||||
'create_index': 1,
|
||||
'server': CommonParams.get('server'),
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'added_fields': 1,
|
||||
'add_fields':1,
|
||||
'index': { 'Key_name':'PRIMARY' },
|
||||
'ajax_request': true
|
||||
};
|
||||
var title = Messages.strAddPrimaryKey;
|
||||
Functions.indexEditorDialog(url, title, function () {
|
||||
// on success
|
||||
$('.sqlqueryresults').remove();
|
||||
$('.result_query').remove();
|
||||
$('.tblFooters').html('');
|
||||
goToStep2('goToStep3');
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('#mainContent').on('click', '#backEditPd', function () {
|
||||
$('#mainContent').html(backup);
|
||||
});
|
||||
$('#mainContent').on('click', '#showPossiblePd', function () {
|
||||
if ($(this).hasClass('hideList')) {
|
||||
$(this).html('+ ' + Messages.strShowPossiblePd);
|
||||
$(this).removeClass('hideList');
|
||||
$('#newCols').slideToggle('slow');
|
||||
return false;
|
||||
}
|
||||
if ($('#newCols').html() !== '') {
|
||||
$('#showPossiblePd').html('- ' + Messages.strHidePd);
|
||||
$('#showPossiblePd').addClass('hideList');
|
||||
$('#newCols').slideToggle('slow');
|
||||
return false;
|
||||
}
|
||||
$('#newCols').insertAfter('#mainContent h4');
|
||||
$('#newCols').html('<div class="text-center">' + Messages.strLoading + '<br>' + Messages.strWaitForPd + '</div>');
|
||||
$.post(
|
||||
'index.php?route=/normalization',
|
||||
{
|
||||
'ajax_request': true,
|
||||
'db': CommonParams.get('db'),
|
||||
'table': CommonParams.get('table'),
|
||||
'server': CommonParams.get('server'),
|
||||
'findPdl': true
|
||||
}, function (data) {
|
||||
$('#showPossiblePd').html('- ' + Messages.strHidePd);
|
||||
$('#showPossiblePd').addClass('hideList');
|
||||
$('#newCols').html(data.message);
|
||||
});
|
||||
});
|
||||
$('#mainContent').on('click', '.pickPd', function () {
|
||||
var strColsLeft = $(this).next('.determinants').html();
|
||||
var colsLeft = strColsLeft.split(',');
|
||||
var strColsRight = $(this).next().next().html();
|
||||
var colsRight = strColsRight.split(',');
|
||||
for (var i in colsRight) {
|
||||
$('form[data-colname="' + colsRight[i].trim() + '"] input[type="checkbox"]').prop('checked', false);
|
||||
for (var j in colsLeft) {
|
||||
$('form[data-colname="' + colsRight[i].trim() + '"] input[value="' + colsLeft[j].trim() + '"]').prop('checked', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @fileoverview Javascript functions used in server replication page
|
||||
* @name Server Replication
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @requires js/functions.js
|
||||
*/
|
||||
|
||||
var randomServerId = Math.floor(Math.random() * 10000000);
|
||||
var confPrefix = 'server-id=' + randomServerId + '\nlog_bin=mysql-bin\nlog_error=mysql-bin.err\n';
|
||||
|
||||
function updateConfig () {
|
||||
var confIgnore = 'binlog_ignore_db=';
|
||||
var confDo = 'binlog_do_db=';
|
||||
var databaseList = '';
|
||||
|
||||
if ($('#db_select option:selected').length === 0) {
|
||||
$('#rep').text(confPrefix);
|
||||
} else if ($('#db_type option:selected').val() === 'all') {
|
||||
$('#db_select option:selected').each(function () {
|
||||
databaseList += confIgnore + $(this).val() + '\n';
|
||||
});
|
||||
$('#rep').text(confPrefix + databaseList);
|
||||
} else {
|
||||
$('#db_select option:selected').each(function () {
|
||||
databaseList += confDo + $(this).val() + '\n';
|
||||
});
|
||||
$('#rep').text(confPrefix + databaseList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('replication.js', function () {
|
||||
$('#db_type').off('change');
|
||||
$('#db_select').off('change');
|
||||
$('#primary_status_href').off('click');
|
||||
$('#primary_replicas_href').off('click');
|
||||
$('#replica_status_href').off('click');
|
||||
$('#replica_control_href').off('click');
|
||||
$('#replica_errormanagement_href').off('click');
|
||||
$('#replica_synchronization_href').off('click');
|
||||
$('#db_reset_href').off('click');
|
||||
$('#db_select_href').off('click');
|
||||
$('#reset_replica').off('click');
|
||||
});
|
||||
|
||||
AJAX.registerOnload('replication.js', function () {
|
||||
$('#rep').text(confPrefix);
|
||||
$('#db_type').on('change', updateConfig);
|
||||
$('#db_select').on('change', updateConfig);
|
||||
|
||||
$('#primary_status_href').on('click', function () {
|
||||
$('#replication_primary_section').toggle();
|
||||
});
|
||||
$('#primary_replicas_href').on('click', function () {
|
||||
$('#replication_replicas_section').toggle();
|
||||
});
|
||||
$('#replica_status_href').on('click', function () {
|
||||
$('#replication_replica_section').toggle();
|
||||
});
|
||||
$('#replica_control_href').on('click', function () {
|
||||
$('#replica_control_gui').toggle();
|
||||
});
|
||||
$('#replica_errormanagement_href').on('click', function () {
|
||||
$('#replica_errormanagement_gui').toggle();
|
||||
});
|
||||
$('#replica_synchronization_href').on('click', function () {
|
||||
$('#replica_synchronization_gui').toggle();
|
||||
});
|
||||
$('#db_reset_href').on('click', function () {
|
||||
$('#db_select option:selected').prop('selected', false);
|
||||
$('#db_select').trigger('change');
|
||||
});
|
||||
$('#db_select_href').on('click', function () {
|
||||
$('#db_select option').prop('selected', true);
|
||||
$('#db_select').trigger('change');
|
||||
});
|
||||
$('#reset_replica').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var $anchor = $(this);
|
||||
var question = Messages.strResetReplicaWarning;
|
||||
$anchor.confirm(question, $anchor.attr('href'), function (url) {
|
||||
Functions.ajaxShowMessage();
|
||||
AJAX.source = $anchor;
|
||||
var params = Functions.getJsConfirmCommonParam({
|
||||
'ajax_page_request': true,
|
||||
'ajax_request': true
|
||||
}, $anchor.getPostData());
|
||||
$.post(url, params, AJAX.responseHandler);
|
||||
});
|
||||
});
|
||||
$('#button_generate_password').on('click', function () {
|
||||
Functions.suggestPassword(this.form);
|
||||
});
|
||||
$('#nopass_1').on('click', function () {
|
||||
this.form.pma_pw.value = '';
|
||||
this.form.pma_pw2.value = '';
|
||||
this.checked = true;
|
||||
});
|
||||
$('#nopass_0').on('click', function () {
|
||||
document.getElementById('text_pma_change_pw').focus();
|
||||
});
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Functions used in server plugins pages
|
||||
*/
|
||||
AJAX.registerOnload('server/plugins.js', function () {
|
||||
// Make columns sortable, but only for tables with more than 1 data row
|
||||
var $tables = $('#plugins_plugins table:has(tbody tr + tr)');
|
||||
$tables.tablesorter({
|
||||
sortList: [[0, 0]],
|
||||
headers: {
|
||||
1: { sorter: false }
|
||||
}
|
||||
});
|
||||
$tables.find('thead th')
|
||||
.append('<div class="sorticon"></div>');
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @fileoverview Javascript functions used in server status query page
|
||||
* @name Server Status Query
|
||||
*
|
||||
* @requires jQuery
|
||||
* @requires jQueryUI
|
||||
* @requires js/functions.js
|
||||
*/
|
||||
|
||||
/* global initTableSorter */ // js/server/status/sorter.js
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('server/status/queries.js', function () {
|
||||
if (document.getElementById('serverstatusquerieschart') !== null) {
|
||||
var queryPieChart = $('#serverstatusquerieschart').data('queryPieChart');
|
||||
if (queryPieChart) {
|
||||
queryPieChart.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AJAX.registerOnload('server/status/queries.js', function () {
|
||||
// Build query statistics chart
|
||||
var cdata = [];
|
||||
try {
|
||||
if (document.getElementById('serverstatusquerieschart') !== null) {
|
||||
$.each($('#serverstatusquerieschart').data('chart'), function (key, value) {
|
||||
cdata.push([key, parseInt(value, 10)]);
|
||||
});
|
||||
$('#serverstatusquerieschart').data(
|
||||
'queryPieChart',
|
||||
Functions.createProfilingChart(
|
||||
'serverstatusquerieschart',
|
||||
cdata
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (exception) {
|
||||
// Could not load chart, no big deal...
|
||||
}
|
||||
|
||||
initTableSorter('statustabs_queries');
|
||||
});
|
@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Functions used in Setup configuration forms
|
||||
*/
|
||||
|
||||
/* global displayErrors, getAllValues, getIdPrefix, validators */ // js/config.js
|
||||
|
||||
// show this window in top frame
|
||||
if (top !== self) {
|
||||
window.top.location.href = location;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Messages
|
||||
//
|
||||
|
||||
$(function () {
|
||||
if (window.location.protocol === 'https:') {
|
||||
$('#no_https').remove();
|
||||
} else {
|
||||
$('#no_https a').on('click', function () {
|
||||
var oldLocation = window.location;
|
||||
window.location.href = 'https:' + oldLocation.href.substring(oldLocation.protocol.length);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
var hiddenMessages = $('.hiddenmessage');
|
||||
|
||||
if (hiddenMessages.length > 0) {
|
||||
hiddenMessages.hide();
|
||||
var link = $('#show_hidden_messages');
|
||||
link.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
hiddenMessages.show();
|
||||
$(this).remove();
|
||||
});
|
||||
link.html(link.html().replace('#MSG_COUNT', hiddenMessages.length));
|
||||
link.show();
|
||||
}
|
||||
});
|
||||
|
||||
// set document width
|
||||
$(function () {
|
||||
var width = 0;
|
||||
$('ul.tabs li').each(function () {
|
||||
width += $(this).width() + 10;
|
||||
});
|
||||
var contentWidth = width;
|
||||
width += 250;
|
||||
$('body').css('min-width', width);
|
||||
$('.tabs_contents').css('min-width', contentWidth);
|
||||
});
|
||||
|
||||
//
|
||||
// END: Messages
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Form validation and field operations
|
||||
//
|
||||
|
||||
/**
|
||||
* Calls server-side validation procedures
|
||||
*
|
||||
* @param {Element} parent input field in <fieldset> or <fieldset>
|
||||
* @param {String} id validator id
|
||||
* @param {object} values values hash {element1_id: value, ...}
|
||||
*
|
||||
* @return {bool|void}
|
||||
*/
|
||||
function ajaxValidate (parent, id, values) {
|
||||
var $parent = $(parent);
|
||||
// ensure that parent is a fieldset
|
||||
if ($parent.attr('tagName') !== 'FIELDSET') {
|
||||
$parent = $parent.closest('fieldset');
|
||||
if ($parent.length === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent.data('ajax') !== null) {
|
||||
$parent.data('ajax').abort();
|
||||
}
|
||||
|
||||
$parent.data('ajax', $.ajax({
|
||||
url: 'validate.php',
|
||||
cache: false,
|
||||
type: 'POST',
|
||||
data: {
|
||||
token: $parent.closest('form').find('input[name=token]').val(),
|
||||
id: id,
|
||||
values: JSON.stringify(values)
|
||||
},
|
||||
success: function (response) {
|
||||
if (response === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var error = {};
|
||||
if (typeof response !== 'object') {
|
||||
error[$parent.id] = [response];
|
||||
} else if (typeof response.error !== 'undefined') {
|
||||
error[$parent.id] = [response.error];
|
||||
} else {
|
||||
for (var key in response) {
|
||||
var value = response[key];
|
||||
error[key] = Array.isArray(value) ? value : [value];
|
||||
}
|
||||
}
|
||||
displayErrors(error);
|
||||
},
|
||||
complete: function () {
|
||||
$parent.removeData('ajax');
|
||||
}
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic form submission on change.
|
||||
*/
|
||||
$(document).on('change', '.autosubmit', function (e) {
|
||||
e.target.form.submit();
|
||||
});
|
||||
|
||||
$.extend(true, validators, {
|
||||
// field validators
|
||||
field: {
|
||||
/**
|
||||
* hide_db field
|
||||
*
|
||||
* @param {boolean} isKeyUp
|
||||
*
|
||||
* @return {true}
|
||||
*/
|
||||
hide_db: function (isKeyUp) { // eslint-disable-line camelcase
|
||||
if (!isKeyUp && this.value !== '') {
|
||||
var data = {};
|
||||
data[this.id] = this.value;
|
||||
ajaxValidate(this, 'Servers/1/hide_db', data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* TrustedProxies field
|
||||
*
|
||||
* @param {boolean} isKeyUp
|
||||
*
|
||||
* @return {true}
|
||||
*/
|
||||
TrustedProxies: function (isKeyUp) {
|
||||
if (!isKeyUp && this.value !== '') {
|
||||
var data = {};
|
||||
data[this.id] = this.value;
|
||||
ajaxValidate(this, 'TrustedProxies', data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
// fieldset validators
|
||||
fieldset: {
|
||||
/**
|
||||
* Validates Server fieldset
|
||||
*
|
||||
* @param {boolean} isKeyUp
|
||||
*
|
||||
* @return {true}
|
||||
*/
|
||||
Server: function (isKeyUp) {
|
||||
if (!isKeyUp) {
|
||||
ajaxValidate(this, 'Server', getAllValues());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Validates Server_login_options fieldset
|
||||
*
|
||||
* @param {boolean} isKeyUp
|
||||
*
|
||||
* @return {true}
|
||||
*/
|
||||
Server_login_options: function (isKeyUp) { // eslint-disable-line camelcase
|
||||
return validators.fieldset.Server.apply(this, [isKeyUp]);
|
||||
},
|
||||
/**
|
||||
* Validates Server_pmadb fieldset
|
||||
*
|
||||
* @param {boolean} isKeyUp
|
||||
*
|
||||
* @return {true}
|
||||
*/
|
||||
Server_pmadb: function (isKeyUp) { // eslint-disable-line camelcase
|
||||
if (isKeyUp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var prefix = getIdPrefix($(this).find('input'));
|
||||
if ($('#' + prefix + 'pmadb').val() !== '') {
|
||||
ajaxValidate(this, 'Server_pmadb', getAllValues());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// END: Form validation and field operations
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// User preferences allow/disallow UI
|
||||
//
|
||||
|
||||
$(function () {
|
||||
$('.userprefs-allow').on('click', function (e) {
|
||||
if (this !== e.target) {
|
||||
return;
|
||||
}
|
||||
var el = $(this).find('input');
|
||||
if (el.prop('disabled')) {
|
||||
return;
|
||||
}
|
||||
el.prop('checked', !el.prop('checked'));
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// END: User preferences allow/disallow UI
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
$(function () {
|
||||
$('.delete-server').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var $this = $(this);
|
||||
$.post($this.attr('href'), $this.attr('data-post'), function () {
|
||||
window.location.replace('index.php');
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('table/find_replace.js', function () {
|
||||
$('#find_replace_form').off('submit');
|
||||
$('#toggle_find').off('click');
|
||||
});
|
||||
|
||||
/**
|
||||
* Bind events
|
||||
*/
|
||||
AJAX.registerOnload('table/find_replace.js', function () {
|
||||
$('<div id="toggle_find_div"><a id="toggle_find"></a></div>')
|
||||
.insertAfter('#find_replace_form')
|
||||
.hide();
|
||||
|
||||
$('#toggle_find')
|
||||
.html(Messages.strHideFindNReplaceCriteria)
|
||||
.on('click', function () {
|
||||
var $link = $(this);
|
||||
$('#find_replace_form').slideToggle();
|
||||
if ($link.text() === Messages.strHideFindNReplaceCriteria) {
|
||||
$link.text(Messages.strShowFindNReplaceCriteria);
|
||||
} else {
|
||||
$link.text(Messages.strHideFindNReplaceCriteria);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#find_replace_form').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var findReplaceForm = $('#find_replace_form');
|
||||
Functions.prepareForAjaxRequest(findReplaceForm);
|
||||
var $msgbox = Functions.ajaxShowMessage();
|
||||
$.post(findReplaceForm.attr('action'), findReplaceForm.serialize(), function (data) {
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
if (data.success === true) {
|
||||
$('#toggle_find_div').show();
|
||||
$('#toggle_find').trigger('click');
|
||||
$('#sqlqueryresultsouter').html(data.preview);
|
||||
} else {
|
||||
$('#sqlqueryresultsouter').html(data.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* for table relation
|
||||
*/
|
||||
|
||||
var TableRelation = {};
|
||||
|
||||
TableRelation.showHideClauses = function ($thisDropdown) {
|
||||
if ($thisDropdown.val() === '') {
|
||||
$thisDropdown.parent().nextAll('span').hide();
|
||||
} else {
|
||||
if ($thisDropdown.is('select[name^="destination_foreign_column"]')) {
|
||||
$thisDropdown.parent().nextAll('span').show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets dropdown options to values
|
||||
* @param $dropdown
|
||||
* @param values
|
||||
* @param selectedValue
|
||||
* @return {void}
|
||||
*/
|
||||
TableRelation.setDropdownValues = function ($dropdown, values, selectedValue) {
|
||||
$dropdown.empty();
|
||||
var optionsAsString = '';
|
||||
// add an empty string to the beginning for empty selection
|
||||
values.unshift('');
|
||||
$.each(values, function () {
|
||||
optionsAsString += '<option value=\'' + Functions.escapeHtml(this) + '\'' + (selectedValue === Functions.escapeHtml(this) ? ' selected=\'selected\'' : '') + '>' + Functions.escapeHtml(this) + '</option>';
|
||||
});
|
||||
$dropdown.append($(optionsAsString));
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves and populates dropdowns to the left based on the selected value
|
||||
*
|
||||
* @param $dropdown the dropdown whose value got changed
|
||||
* @return {void}
|
||||
*/
|
||||
TableRelation.getDropdownValues = function ($dropdown) {
|
||||
var foreignDb = null;
|
||||
var foreignTable = null;
|
||||
var $databaseDd;
|
||||
var $tableDd;
|
||||
var $columnDd;
|
||||
var foreign = '';
|
||||
// if the changed dropdown is for foreign key constraints
|
||||
if ($dropdown.is('select[name^="destination_foreign"]')) {
|
||||
$databaseDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_db"]');
|
||||
$tableDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_table"]');
|
||||
$columnDd = $dropdown.parent().parent().parent().find('select[name^="destination_foreign_column"]');
|
||||
foreign = '_foreign';
|
||||
} else { // internal relations
|
||||
$databaseDd = $dropdown.parent().find('select[name^="destination_db"]');
|
||||
$tableDd = $dropdown.parent().find('select[name^="destination_table"]');
|
||||
$columnDd = $dropdown.parent().find('select[name^="destination_column"]');
|
||||
}
|
||||
|
||||
// if the changed dropdown is a database selector
|
||||
if ($dropdown.is('select[name^="destination' + foreign + '_db"]')) {
|
||||
foreignDb = $dropdown.val();
|
||||
// if no database is selected empty table and column dropdowns
|
||||
if (foreignDb === '') {
|
||||
TableRelation.setDropdownValues($tableDd, []);
|
||||
TableRelation.setDropdownValues($columnDd, []);
|
||||
return;
|
||||
}
|
||||
} else { // if a table selector
|
||||
foreignDb = $databaseDd.val();
|
||||
foreignTable = $dropdown.val();
|
||||
// if no table is selected empty the column dropdown
|
||||
if (foreignTable === '') {
|
||||
TableRelation.setDropdownValues($columnDd, []);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var $msgbox = Functions.ajaxShowMessage();
|
||||
var $form = $dropdown.parents('form');
|
||||
var $db = $form.find('input[name="db"]').val();
|
||||
var $table = $form.find('input[name="table"]').val();
|
||||
var argsep = CommonParams.get('arg_separator');
|
||||
var params = 'getDropdownValues=true' + argsep + 'ajax_request=true' +
|
||||
argsep + 'db=' + encodeURIComponent($db) +
|
||||
argsep + 'table=' + encodeURIComponent($table) +
|
||||
argsep + 'foreign=' + (foreign !== '') +
|
||||
argsep + 'foreignDb=' + encodeURIComponent(foreignDb) +
|
||||
(foreignTable !== null ?
|
||||
argsep + 'foreignTable=' + encodeURIComponent(foreignTable) : ''
|
||||
);
|
||||
var $server = $form.find('input[name="server"]');
|
||||
if ($server.length > 0) {
|
||||
params += argsep + 'server=' + $form.find('input[name="server"]').val();
|
||||
}
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?route=/table/relation',
|
||||
data: params,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
Functions.ajaxRemoveMessage($msgbox);
|
||||
if (typeof data !== 'undefined' && data.success) {
|
||||
// if the changed dropdown is a database selector
|
||||
if (foreignTable === null) {
|
||||
// set values for table and column dropdowns
|
||||
TableRelation.setDropdownValues($tableDd, data.tables);
|
||||
TableRelation.setDropdownValues($columnDd, []);
|
||||
} else { // if a table selector
|
||||
// set values for the column dropdown
|
||||
var primary = null;
|
||||
if (typeof data.primary !== 'undefined'
|
||||
&& 1 === data.primary.length
|
||||
) {
|
||||
primary = data.primary[0];
|
||||
}
|
||||
TableRelation.setDropdownValues($columnDd.first(), data.columns, primary);
|
||||
TableRelation.setDropdownValues($columnDd.slice(1), data.columns);
|
||||
}
|
||||
} else {
|
||||
Functions.ajaxShowMessage(data.error, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind all event handlers before tearing down a page
|
||||
*/
|
||||
AJAX.registerTeardown('table/relation.js', function () {
|
||||
$('body').off('change',
|
||||
'select[name^="destination_db"], ' +
|
||||
'select[name^="destination_table"], ' +
|
||||
'select[name^="destination_foreign_db"], ' +
|
||||
'select[name^="destination_foreign_table"]'
|
||||
);
|
||||
$('body').off('click', 'a.add_foreign_key_field');
|
||||
$('body').off('click', 'a.add_foreign_key');
|
||||
$('a.drop_foreign_key_anchor.ajax').off('click');
|
||||
});
|
||||
|
||||
AJAX.registerOnload('table/relation.js', function () {
|
||||
/**
|
||||
* Ajax event handler to fetch table/column dropdown values.
|
||||
*/
|
||||
$('body').on('change',
|
||||
'select[name^="destination_db"], ' +
|
||||
'select[name^="destination_table"], ' +
|
||||
'select[name^="destination_foreign_db"], ' +
|
||||
'select[name^="destination_foreign_table"]',
|
||||
function () {
|
||||
TableRelation.getDropdownValues($(this));
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Ajax event handler to add a column to a foreign key constraint.
|
||||
*/
|
||||
$('body').on('click', 'a.add_foreign_key_field', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Add field.
|
||||
$(this)
|
||||
.prev('span')
|
||||
.clone(true, true)
|
||||
.insertBefore($(this))
|
||||
.find('select')
|
||||
.val('');
|
||||
|
||||
// Add foreign field.
|
||||
var $sourceElem = $('select[name^="destination_foreign_column[' +
|
||||
$(this).attr('data-index') + ']"]').last().parent();
|
||||
$sourceElem
|
||||
.clone(true, true)
|
||||
.insertAfter($sourceElem)
|
||||
.find('select')
|
||||
.val('');
|
||||
});
|
||||
|
||||
/**
|
||||
* Ajax event handler to add a foreign key constraint.
|
||||
*/
|
||||
$('body').on('click', 'a.add_foreign_key', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var $prevRow = $(this).closest('tr').prev('tr');
|
||||
var $newRow = $prevRow.clone(true, true);
|
||||
|
||||
// Update serial number.
|
||||
var currIndex = $newRow
|
||||
.find('a.add_foreign_key_field')
|
||||
.attr('data-index');
|
||||
var newIndex = parseInt(currIndex) + 1;
|
||||
$newRow.find('a.add_foreign_key_field').attr('data-index', newIndex);
|
||||
|
||||
// Update form parameter names.
|
||||
$newRow.find('select[name^="foreign_key_fields_name"]')
|
||||
.not($newRow.find('select[name^="foreign_key_fields_name"]').first())
|
||||
.find('select[name^="destination_foreign_column"]')
|
||||
.not($newRow.find('select[name^="foreign_key_fields_name"]')
|
||||
.not($newRow.find('select[name^="foreign_key_fields_name"]').first())
|
||||
.find('select[name^="destination_foreign_column"]').first()
|
||||
).each(function () {
|
||||
$(this).parent().remove();
|
||||
});
|
||||
$newRow.find('input, select').each(function () {
|
||||
$(this).attr('name',
|
||||
$(this).attr('name').replace(/\d/, newIndex)
|
||||
);
|
||||
});
|
||||
$newRow.find('input[type="text"]').each(function () {
|
||||
$(this).val('');
|
||||
});
|
||||
// Finally add the row.
|
||||
$newRow.insertAfter($prevRow);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ajax Event handler for 'Drop Foreign key'
|
||||
*/
|
||||
$('a.drop_foreign_key_anchor.ajax').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
var $anchor = $(this);
|
||||
|
||||
// Object containing reference to the current field's row
|
||||
var $currRow = $anchor.parents('tr');
|
||||
|
||||
var dropQuery = Functions.escapeHtml(
|
||||
$currRow.children('td')
|
||||
.children('.drop_foreign_key_msg')
|
||||
.val()
|
||||
);
|
||||
|
||||
var question = Functions.sprintf(Messages.strDoYouReally, dropQuery);
|
||||
|
||||
$anchor.confirm(question, $anchor.attr('href'), function (url) {
|
||||
var $msg = Functions.ajaxShowMessage(Messages.strDroppingForeignKey, false);
|
||||
var params = Functions.getJsConfirmCommonParam(this, $anchor.getPostData());
|
||||
$.post(url, params, function (data) {
|
||||
if (data.success === true) {
|
||||
Functions.ajaxRemoveMessage($msg);
|
||||
CommonActions.refreshMain(false, function () {
|
||||
// Do nothing
|
||||
});
|
||||
} else {
|
||||
Functions.ajaxShowMessage(Messages.strErrorProcessingRequest + ' : ' + data.error, false);
|
||||
}
|
||||
}); // end $.post()
|
||||
});
|
||||
}); // end Drop Foreign key
|
||||
|
||||
var windowWidth = $(window).width();
|
||||
$('.jsresponsive').css('max-width', (windowWidth - 35) + 'px');
|
||||
});
|
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* SQL syntax highlighting transformation plugin js
|
||||
*
|
||||
* @package PhpMyAdmin
|
||||
*/
|
||||
AJAX.registerOnload('transformations/sql_editor.js', function () {
|
||||
$('textarea.transform_sql_editor').each(function () {
|
||||
Functions.getSqlEditor($(this), {}, 'both');
|
||||
});
|
||||
});
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,76 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.runMode = function(string, modespec, callback, options) {
|
||||
var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
|
||||
var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
|
||||
|
||||
// Create a tokenizing callback function if passed-in callback is a DOM element.
|
||||
if (callback.appendChild) {
|
||||
var ie = /MSIE \d/.test(navigator.userAgent);
|
||||
var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
|
||||
var node = callback, col = 0;
|
||||
node.innerHTML = "";
|
||||
callback = function(text, style) {
|
||||
if (text == "\n") {
|
||||
// Emitting LF or CRLF on IE8 or earlier results in an incorrect display.
|
||||
// Emitting a carriage return makes everything ok.
|
||||
node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text));
|
||||
col = 0;
|
||||
return;
|
||||
}
|
||||
var content = "";
|
||||
// replace tabs
|
||||
for (var pos = 0;;) {
|
||||
var idx = text.indexOf("\t", pos);
|
||||
if (idx == -1) {
|
||||
content += text.slice(pos);
|
||||
col += text.length - pos;
|
||||
break;
|
||||
} else {
|
||||
col += idx - pos;
|
||||
content += text.slice(pos, idx);
|
||||
var size = tabSize - col % tabSize;
|
||||
col += size;
|
||||
for (var i = 0; i < size; ++i) content += " ";
|
||||
pos = idx + 1;
|
||||
}
|
||||
}
|
||||
// Create a node with token style and append it to the callback DOM element.
|
||||
if (style) {
|
||||
var sp = node.appendChild(document.createElement("span"));
|
||||
sp.className = "cm-" + style.replace(/ +/g, " cm-");
|
||||
sp.appendChild(document.createTextNode(content));
|
||||
} else {
|
||||
node.appendChild(document.createTextNode(content));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode);
|
||||
for (var i = 0, e = lines.length; i < e; ++i) {
|
||||
if (i) callback("\n");
|
||||
var stream = new CodeMirror.StringStream(lines[i], null, {
|
||||
lookAhead: function(n) { return lines[i + n] },
|
||||
baseToken: function() {}
|
||||
});
|
||||
if (!stream.string && mode.blankLine) mode.blankLine(state);
|
||||
while (!stream.eol()) {
|
||||
var style = mode.token(stream, state);
|
||||
callback(stream.current(), style, i, stream.start, state, mode);
|
||||
stream.start = stream.pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
@ -0,0 +1,203 @@
|
||||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.9
|
||||
* Revision: dff2f04
|
||||
*
|
||||
* Copyright (c) 2009-2016 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
/**
|
||||
* Class: $.jqplot.CanvasAxisLabelRenderer
|
||||
* Renderer to draw axis labels with a canvas element to support advanced
|
||||
* featrues such as rotated text. This renderer uses a separate rendering engine
|
||||
* to draw the text on the canvas. Two modes of rendering the text are available.
|
||||
* If the browser has native font support for canvas fonts (currently Mozila 3.5
|
||||
* and Safari 4), you can enable text rendering with the canvas fillText method.
|
||||
* You do so by setting the "enableFontSupport" option to true.
|
||||
*
|
||||
* Browsers lacking native font support will have the text drawn on the canvas
|
||||
* using the Hershey font metrics. Even if the "enableFontSupport" option is true
|
||||
* non-supporting browsers will still render with the Hershey font.
|
||||
*
|
||||
*/
|
||||
$.jqplot.CanvasAxisLabelRenderer = function(options) {
|
||||
// Group: Properties
|
||||
|
||||
// prop: angle
|
||||
// angle of text, measured clockwise from x axis.
|
||||
this.angle = 0;
|
||||
// name of the axis associated with this tick
|
||||
this.axis;
|
||||
// prop: show
|
||||
// whether or not to show the tick (mark and label).
|
||||
this.show = true;
|
||||
// prop: showLabel
|
||||
// whether or not to show the label.
|
||||
this.showLabel = true;
|
||||
// prop: label
|
||||
// label for the axis.
|
||||
this.label = '';
|
||||
// prop: fontFamily
|
||||
// CSS spec for the font-family css attribute.
|
||||
// Applies only to browsers supporting native font rendering in the
|
||||
// canvas tag. Currently Mozilla 3.5 and Safari 4.
|
||||
this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
|
||||
// prop: fontSize
|
||||
// CSS spec for font size.
|
||||
this.fontSize = '11pt';
|
||||
// prop: fontWeight
|
||||
// CSS spec for fontWeight: normal, bold, bolder, lighter or a number 100 - 900
|
||||
this.fontWeight = 'normal';
|
||||
// prop: fontStretch
|
||||
// Multiplier to condense or expand font width.
|
||||
// Applies only to browsers which don't support canvas native font rendering.
|
||||
this.fontStretch = 1.0;
|
||||
// prop: textColor
|
||||
// css spec for the color attribute.
|
||||
this.textColor = '#666666';
|
||||
// prop: enableFontSupport
|
||||
// true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
|
||||
// If true, label will be drawn with canvas tag native support for fonts.
|
||||
// If false, label will be drawn with Hershey font metrics.
|
||||
this.enableFontSupport = true;
|
||||
// prop: pt2px
|
||||
// Point to pixel scaling factor, used for computing height of bounding box
|
||||
// around a label. The labels text renderer has a default setting of 1.4, which
|
||||
// should be suitable for most fonts. Leave as null to use default. If tops of
|
||||
// letters appear clipped, increase this. If bounding box seems too big, decrease.
|
||||
// This is an issue only with the native font renderering capabilities of Mozilla
|
||||
// 3.5 and Safari 4 since they do not provide a method to determine the font height.
|
||||
this.pt2px = null;
|
||||
|
||||
this._elem;
|
||||
this._ctx;
|
||||
this._plotWidth;
|
||||
this._plotHeight;
|
||||
this._plotDimensions = {height:null, width:null};
|
||||
|
||||
$.extend(true, this, options);
|
||||
|
||||
if (options.angle == null && this.axis != 'xaxis' && this.axis != 'x2axis') {
|
||||
this.angle = -90;
|
||||
}
|
||||
|
||||
var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
|
||||
if (this.pt2px) {
|
||||
ropts.pt2px = this.pt2px;
|
||||
}
|
||||
|
||||
if (this.enableFontSupport) {
|
||||
if ($.jqplot.support_canvas_text()) {
|
||||
this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
|
||||
}
|
||||
|
||||
else {
|
||||
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisLabelRenderer.prototype.init = function(options) {
|
||||
$.extend(true, this, options);
|
||||
this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
|
||||
};
|
||||
|
||||
// return width along the x axis
|
||||
// will check first to see if an element exists.
|
||||
// if not, will return the computed text box width.
|
||||
$.jqplot.CanvasAxisLabelRenderer.prototype.getWidth = function(ctx) {
|
||||
if (this._elem) {
|
||||
return this._elem.outerWidth(true);
|
||||
}
|
||||
else {
|
||||
var tr = this._textRenderer;
|
||||
var l = tr.getWidth(ctx);
|
||||
var h = tr.getHeight(ctx);
|
||||
var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
|
||||
return w;
|
||||
}
|
||||
};
|
||||
|
||||
// return height along the y axis.
|
||||
$.jqplot.CanvasAxisLabelRenderer.prototype.getHeight = function(ctx) {
|
||||
if (this._elem) {
|
||||
return this._elem.outerHeight(true);
|
||||
}
|
||||
else {
|
||||
var tr = this._textRenderer;
|
||||
var l = tr.getWidth(ctx);
|
||||
var h = tr.getHeight(ctx);
|
||||
var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
|
||||
return w;
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisLabelRenderer.prototype.getAngleRad = function() {
|
||||
var a = this.angle * Math.PI/180;
|
||||
return a;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisLabelRenderer.prototype.draw = function(ctx, plot) {
|
||||
// Memory Leaks patch
|
||||
if (this._elem) {
|
||||
if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
|
||||
window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
|
||||
}
|
||||
|
||||
this._elem.emptyForce();
|
||||
this._elem = null;
|
||||
}
|
||||
|
||||
// create a canvas here, but can't draw on it untill it is appended
|
||||
// to dom for IE compatability.
|
||||
var elem = plot.canvasManager.getCanvas();
|
||||
|
||||
this._textRenderer.setText(this.label, ctx);
|
||||
var w = this.getWidth(ctx);
|
||||
var h = this.getHeight(ctx);
|
||||
elem.width = w;
|
||||
elem.height = h;
|
||||
elem.style.width = w;
|
||||
elem.style.height = h;
|
||||
|
||||
elem = plot.canvasManager.initCanvas(elem);
|
||||
|
||||
this._elem = $(elem);
|
||||
this._elem.css({ position: 'absolute'});
|
||||
this._elem.addClass('jqplot-'+this.axis+'-label');
|
||||
|
||||
elem = null;
|
||||
return this._elem;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisLabelRenderer.prototype.pack = function() {
|
||||
this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
|
||||
};
|
||||
|
||||
})(jQuery);
|
@ -0,0 +1,484 @@
|
||||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.9
|
||||
* Revision: dff2f04
|
||||
*
|
||||
* Copyright (c) 2009-2016 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
$.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
|
||||
|
||||
/**
|
||||
* Class: $.jqplot.Highlighter
|
||||
* Plugin which will highlight data points when they are moused over.
|
||||
*
|
||||
* To use this plugin, include the js
|
||||
* file in your source:
|
||||
*
|
||||
* > <script type="text/javascript" src="plugins/jqplot.highlighter.js"></script>
|
||||
*
|
||||
* A tooltip providing information about the data point is enabled by default.
|
||||
* To disable the tooltip, set "showTooltip" to false.
|
||||
*
|
||||
* You can control what data is displayed in the tooltip with various
|
||||
* options. The "tooltipAxes" option controls whether the x, y or both
|
||||
* data values are displayed.
|
||||
*
|
||||
* Some chart types (e.g. hi-low-close) have more than one y value per
|
||||
* data point. To display the additional values in the tooltip, set the
|
||||
* "yvalues" option to the desired number of y values present (3 for a hlc chart).
|
||||
*
|
||||
* By default, data values will be formatted with the same formatting
|
||||
* specifiers as used to format the axis ticks. A custom format code
|
||||
* can be supplied with the tooltipFormatString option. This will apply
|
||||
* to all values in the tooltip.
|
||||
*
|
||||
* For more complete control, the "formatString" option can be set. This
|
||||
* Allows conplete control over tooltip formatting. Values are passed to
|
||||
* the format string in an order determined by the "tooltipAxes" and "yvalues"
|
||||
* options. So, if you have a hi-low-close chart and you just want to display
|
||||
* the hi-low-close values in the tooltip, you could set a formatString like:
|
||||
*
|
||||
* > highlighter: {
|
||||
* > tooltipAxes: 'y',
|
||||
* > yvalues: 3,
|
||||
* > formatString:'<table class="jqplot-highlighter">
|
||||
* > <tr><td>hi:</td><td>%s</td></tr>
|
||||
* > <tr><td>low:</td><td>%s</td></tr>
|
||||
* > <tr><td>close:</td><td>%s</td></tr></table>'
|
||||
* > }
|
||||
*
|
||||
*/
|
||||
$.jqplot.Highlighter = function(options) {
|
||||
// Group: Properties
|
||||
//
|
||||
//prop: show
|
||||
// true to show the highlight.
|
||||
this.show = $.jqplot.config.enablePlugins;
|
||||
// prop: markerRenderer
|
||||
// Renderer used to draw the marker of the highlighted point.
|
||||
// Renderer will assimilate attributes from the data point being highlighted,
|
||||
// so no attributes need set on the renderer directly.
|
||||
// Default is to turn off shadow drawing on the highlighted point.
|
||||
this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
|
||||
// prop: showMarker
|
||||
// true to show the marker
|
||||
this.showMarker = true;
|
||||
// prop: lineWidthAdjust
|
||||
// Pixels to add to the lineWidth of the highlight.
|
||||
this.lineWidthAdjust = 2.5;
|
||||
// prop: sizeAdjust
|
||||
// Pixels to add to the overall size of the highlight.
|
||||
this.sizeAdjust = 5;
|
||||
// prop: showTooltip
|
||||
// Show a tooltip with data point values.
|
||||
this.showTooltip = true;
|
||||
// prop: tooltipLocation
|
||||
// Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
|
||||
this.tooltipLocation = 'nw';
|
||||
// prop: fadeTooltip
|
||||
// true = fade in/out tooltip, flase = show/hide tooltip
|
||||
this.fadeTooltip = true;
|
||||
// prop: tooltipFadeSpeed
|
||||
// 'slow', 'def', 'fast', or number of milliseconds.
|
||||
this.tooltipFadeSpeed = "fast";
|
||||
// prop: tooltipOffset
|
||||
// Pixel offset of tooltip from the highlight.
|
||||
this.tooltipOffset = 2;
|
||||
// prop: tooltipAxes
|
||||
// Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx'
|
||||
// 'both' and 'xy' are equivalent, 'yx' reverses order of labels.
|
||||
this.tooltipAxes = 'both';
|
||||
// prop; tooltipSeparator
|
||||
// String to use to separate x and y axes in tooltip.
|
||||
this.tooltipSeparator = ', ';
|
||||
// prop; tooltipContentEditor
|
||||
// Function used to edit/augment/replace the formatted tooltip contents.
|
||||
// Called as str = tooltipContentEditor(str, seriesIndex, pointIndex)
|
||||
// where str is the generated tooltip html and seriesIndex and pointIndex identify
|
||||
// the data point being highlighted. Should return the html for the tooltip contents.
|
||||
this.tooltipContentEditor = null;
|
||||
// prop: useAxesFormatters
|
||||
// Use the x and y axes formatters to format the text in the tooltip.
|
||||
this.useAxesFormatters = true;
|
||||
// prop: tooltipFormatString
|
||||
// sprintf format string for the tooltip.
|
||||
// Uses Ash Searle's javascript sprintf implementation
|
||||
// found here: http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
// See http://perldoc.perl.org/functions/sprintf.html for reference.
|
||||
// Additional "p" and "P" format specifiers added by Chris Leonello.
|
||||
this.tooltipFormatString = '%.5P';
|
||||
// prop: formatString
|
||||
// alternative to tooltipFormatString
|
||||
// will format the whole tooltip text, populating with x, y values as
|
||||
// indicated by tooltipAxes option. So, you could have a tooltip like:
|
||||
// 'Date: %s, number of cats: %d' to format the whole tooltip at one go.
|
||||
// If useAxesFormatters is true, values will be formatted according to
|
||||
// Axes formatters and you can populate your tooltip string with
|
||||
// %s placeholders.
|
||||
this.formatString = null;
|
||||
// prop: yvalues
|
||||
// Number of y values to expect in the data point array.
|
||||
// Typically this is 1. Certain plots, like OHLC, will
|
||||
// have more y values in each data point array.
|
||||
this.yvalues = 1;
|
||||
// prop: bringSeriesToFront
|
||||
// This option requires jQuery 1.4+
|
||||
// True to bring the series of the highlighted point to the front
|
||||
// of other series.
|
||||
this.bringSeriesToFront = false;
|
||||
this._tooltipElem;
|
||||
this.isHighlighting = false;
|
||||
this.currentNeighbor = null;
|
||||
|
||||
$.extend(true, this, options);
|
||||
};
|
||||
|
||||
var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
|
||||
var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
|
||||
var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
|
||||
|
||||
// axis.renderer.tickrenderer.formatter
|
||||
|
||||
// called with scope of plot
|
||||
$.jqplot.Highlighter.init = function (target, data, opts){
|
||||
var options = opts || {};
|
||||
// add a highlighter attribute to the plot
|
||||
this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter);
|
||||
};
|
||||
|
||||
// called within scope of series
|
||||
$.jqplot.Highlighter.parseOptions = function (defaults, options) {
|
||||
// Add a showHighlight option to the series
|
||||
// and set it to true by default.
|
||||
this.showHighlight = true;
|
||||
};
|
||||
|
||||
// called within context of plot
|
||||
// create a canvas which we can draw on.
|
||||
// insert it before the eventCanvas, so eventCanvas will still capture events.
|
||||
$.jqplot.Highlighter.postPlotDraw = function() {
|
||||
// Memory Leaks patch
|
||||
if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) {
|
||||
this.plugins.highlighter.highlightCanvas.resetCanvas();
|
||||
this.plugins.highlighter.highlightCanvas = null;
|
||||
}
|
||||
|
||||
if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) {
|
||||
this.plugins.highlighter._tooltipElem.emptyForce();
|
||||
this.plugins.highlighter._tooltipElem = null;
|
||||
}
|
||||
|
||||
this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas();
|
||||
|
||||
this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this));
|
||||
this.plugins.highlighter.highlightCanvas.setContext();
|
||||
|
||||
var elem = document.createElement('div');
|
||||
this.plugins.highlighter._tooltipElem = $(elem);
|
||||
elem = null;
|
||||
this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip');
|
||||
this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'});
|
||||
|
||||
this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem);
|
||||
};
|
||||
|
||||
$.jqplot.preInitHooks.push($.jqplot.Highlighter.init);
|
||||
$.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions);
|
||||
$.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw);
|
||||
|
||||
function draw(plot, neighbor) {
|
||||
var hl = plot.plugins.highlighter;
|
||||
var s = plot.series[neighbor.seriesIndex];
|
||||
var smr = s.markerRenderer;
|
||||
var mr = hl.markerRenderer;
|
||||
mr.style = smr.style;
|
||||
mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust;
|
||||
mr.size = smr.size + hl.sizeAdjust;
|
||||
var rgba = $.jqplot.getColorComponents(smr.color);
|
||||
var newrgb = [rgba[0], rgba[1], rgba[2]];
|
||||
var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
|
||||
mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
|
||||
mr.init();
|
||||
var x_pos = s.gridData[neighbor.pointIndex][0];
|
||||
var y_pos = s.gridData[neighbor.pointIndex][1];
|
||||
// Adjusting with s._barNudge
|
||||
if (s.renderer.constructor == $.jqplot.BarRenderer) {
|
||||
if (s.barDirection == "vertical") {
|
||||
x_pos += s._barNudge;
|
||||
}
|
||||
else {
|
||||
y_pos -= s._barNudge;
|
||||
}
|
||||
}
|
||||
mr.draw(x_pos, y_pos, hl.highlightCanvas._ctx);
|
||||
}
|
||||
|
||||
function showTooltip(plot, series, neighbor) {
|
||||
// neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}
|
||||
// gridData should be x,y pixel coords on the grid.
|
||||
// add the plot._gridPadding to that to get x,y in the target.
|
||||
var hl = plot.plugins.highlighter;
|
||||
var elem = hl._tooltipElem;
|
||||
var serieshl = series.highlighter || {};
|
||||
|
||||
var opts = $.extend(true, {}, hl, serieshl);
|
||||
|
||||
if (opts.useAxesFormatters) {
|
||||
var xf = series._xaxis._ticks[0].formatter;
|
||||
var yf = series._yaxis._ticks[0].formatter;
|
||||
var xfstr = series._xaxis._ticks[0].formatString;
|
||||
var yfstr = series._yaxis._ticks[0].formatString;
|
||||
var str;
|
||||
var xstr = xf(xfstr, neighbor.data[0]);
|
||||
var ystrs = [];
|
||||
for (var i=1; i<opts.yvalues+1; i++) {
|
||||
ystrs.push(yf(yfstr, neighbor.data[i]));
|
||||
}
|
||||
if (typeof opts.formatString === 'string') {
|
||||
switch (opts.tooltipAxes) {
|
||||
case 'both':
|
||||
case 'xy':
|
||||
ystrs.unshift(xstr);
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
case 'yx':
|
||||
ystrs.push(xstr);
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
case 'x':
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString, xstr]);
|
||||
break;
|
||||
case 'y':
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
default: // same as xy
|
||||
ystrs.unshift(xstr);
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (opts.tooltipAxes) {
|
||||
case 'both':
|
||||
case 'xy':
|
||||
str = xstr;
|
||||
for (var i=0; i<ystrs.length; i++) {
|
||||
str += opts.tooltipSeparator + ystrs[i];
|
||||
}
|
||||
break;
|
||||
case 'yx':
|
||||
str = '';
|
||||
for (var i=0; i<ystrs.length; i++) {
|
||||
str += ystrs[i] + opts.tooltipSeparator;
|
||||
}
|
||||
str += xstr;
|
||||
break;
|
||||
case 'x':
|
||||
str = xstr;
|
||||
break;
|
||||
case 'y':
|
||||
str = ystrs.join(opts.tooltipSeparator);
|
||||
break;
|
||||
default: // same as 'xy'
|
||||
str = xstr;
|
||||
for (var i=0; i<ystrs.length; i++) {
|
||||
str += opts.tooltipSeparator + ystrs[i];
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var str;
|
||||
if (typeof opts.formatString === 'string') {
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString].concat(neighbor.data));
|
||||
}
|
||||
|
||||
else {
|
||||
if (opts.tooltipAxes == 'both' || opts.tooltipAxes == 'xy') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
|
||||
}
|
||||
else if (opts.tooltipAxes == 'yx') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
|
||||
}
|
||||
else if (opts.tooltipAxes == 'x') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
|
||||
}
|
||||
else if (opts.tooltipAxes == 'y') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($.isFunction(opts.tooltipContentEditor)) {
|
||||
// args str, seriesIndex, pointIndex are essential so the hook can look up
|
||||
// extra data for the point.
|
||||
str = opts.tooltipContentEditor(str, neighbor.seriesIndex, neighbor.pointIndex, plot);
|
||||
}
|
||||
elem.html(str);
|
||||
var gridpos = {x:neighbor.gridData[0], y:neighbor.gridData[1]};
|
||||
var ms = 0;
|
||||
var fact = 0.707;
|
||||
if (series.markerRenderer.show == true) {
|
||||
ms = (series.markerRenderer.size + opts.sizeAdjust)/2;
|
||||
}
|
||||
|
||||
var loc = locations;
|
||||
if (series.fillToZero && series.fill && neighbor.data[1] < 0) {
|
||||
loc = oppositeLocations;
|
||||
}
|
||||
|
||||
switch (loc[locationIndicies[opts.tooltipLocation]]) {
|
||||
case 'nw':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
|
||||
break;
|
||||
case 'n':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - ms;
|
||||
break;
|
||||
case 'ne':
|
||||
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
|
||||
break;
|
||||
case 'e':
|
||||
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
|
||||
break;
|
||||
case 'se':
|
||||
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
|
||||
break;
|
||||
case 's':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
|
||||
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + ms;
|
||||
break;
|
||||
case 'sw':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
|
||||
break;
|
||||
case 'w':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
|
||||
break;
|
||||
default: // same as 'nw'
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
|
||||
break;
|
||||
}
|
||||
if (series.renderer.constructor == $.jqplot.BarRenderer) {
|
||||
if (series.barDirection == 'vertical') {
|
||||
x += series._barNudge;
|
||||
}
|
||||
else {
|
||||
y -= series._barNudge;
|
||||
}
|
||||
}
|
||||
elem.css('left', x);
|
||||
elem.css('top', y);
|
||||
if (opts.fadeTooltip) {
|
||||
// Fix for stacked up animations. Thnanks Trevor!
|
||||
elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
|
||||
}
|
||||
else {
|
||||
elem.show();
|
||||
}
|
||||
elem = null;
|
||||
|
||||
}
|
||||
|
||||
function handleMove(ev, gridpos, datapos, neighbor, plot) {
|
||||
var hl = plot.plugins.highlighter;
|
||||
var c = plot.plugins.cursor;
|
||||
if (hl.show) {
|
||||
if (neighbor == null && hl.isHighlighting) {
|
||||
var evt = jQuery.Event('jqplotHighlighterUnhighlight');
|
||||
plot.target.trigger(evt);
|
||||
|
||||
var ctx = hl.highlightCanvas._ctx;
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
if (hl.fadeTooltip) {
|
||||
hl._tooltipElem.fadeOut(hl.tooltipFadeSpeed);
|
||||
}
|
||||
else {
|
||||
hl._tooltipElem.hide();
|
||||
}
|
||||
if (hl.bringSeriesToFront) {
|
||||
plot.restorePreviousSeriesOrder();
|
||||
}
|
||||
hl.isHighlighting = false;
|
||||
hl.currentNeighbor = null;
|
||||
ctx = null;
|
||||
}
|
||||
else if (neighbor != null && plot.series[neighbor.seriesIndex].showHighlight && !hl.isHighlighting) {
|
||||
var evt = jQuery.Event('jqplotHighlighterHighlight');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data, plot];
|
||||
plot.target.trigger(evt, ins);
|
||||
|
||||
hl.isHighlighting = true;
|
||||
hl.currentNeighbor = neighbor;
|
||||
if (hl.showMarker) {
|
||||
draw(plot, neighbor);
|
||||
}
|
||||
if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
|
||||
showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
|
||||
}
|
||||
if (hl.bringSeriesToFront) {
|
||||
plot.moveSeriesToFront(neighbor.seriesIndex);
|
||||
}
|
||||
}
|
||||
// check to see if we're highlighting the wrong point.
|
||||
else if (neighbor != null && hl.isHighlighting && hl.currentNeighbor != neighbor) {
|
||||
// highlighting the wrong point.
|
||||
|
||||
// if new series allows highlighting, highlight new point.
|
||||
if (plot.series[neighbor.seriesIndex].showHighlight) {
|
||||
var ctx = hl.highlightCanvas._ctx;
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
hl.isHighlighting = true;
|
||||
hl.currentNeighbor = neighbor;
|
||||
if (hl.showMarker) {
|
||||
draw(plot, neighbor);
|
||||
}
|
||||
if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
|
||||
showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
|
||||
}
|
||||
if (hl.bringSeriesToFront) {
|
||||
plot.moveSeriesToFront(neighbor.seriesIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery);
|
@ -0,0 +1,20 @@
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Debounce and throttle function's decorator plugin 1.0.6
|
||||
*
|
||||
* Copyright (c) 2009 Filatov Dmitry (alpha@zforms.ru)
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
$.extend({
|
||||
|
||||
/**
|
||||
* Debounce's decorator
|
||||
* @param {Function} fn original function
|
||||
* @param {Number} timeout timeout
|
||||
* @param {Boolean} [invokeAsap=false] invoke function as soon as possible
|
||||
* @param {Object} [ctx] context of original function
|
||||
*/
|
||||
debounce: function (fn, timeout, invokeAsap, ctx) {
|
||||
|
||||
if (arguments.length == 3 && typeof invokeAsap != 'boolean') {
|
||||
ctx = invokeAsap;
|
||||
invokeAsap = false;
|
||||
}
|
||||
|
||||
var timer;
|
||||
|
||||
return function () {
|
||||
|
||||
var args = arguments;
|
||||
ctx = ctx || this;
|
||||
|
||||
invokeAsap && !timer && fn.apply(ctx, args);
|
||||
|
||||
clearTimeout(timer);
|
||||
|
||||
timer = setTimeout(function () {
|
||||
invokeAsap || fn.apply(ctx, args);
|
||||
timer = null;
|
||||
}, timeout);
|
||||
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Throttle's decorator
|
||||
* @param {Function} fn original function
|
||||
* @param {Number} timeout timeout
|
||||
* @param {Object} [ctx] context of original function
|
||||
*/
|
||||
throttle: function (fn, timeout, ctx) {
|
||||
|
||||
var timer, args, needInvoke;
|
||||
|
||||
return function () {
|
||||
|
||||
args = arguments;
|
||||
needInvoke = true;
|
||||
ctx = ctx || this;
|
||||
|
||||
timer || (function () {
|
||||
if (needInvoke) {
|
||||
fn.apply(ctx, args);
|
||||
needInvoke = false;
|
||||
timer = setTimeout(arguments.callee, timeout);
|
||||
}
|
||||
else {
|
||||
timer = null;
|
||||
}
|
||||
})();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(jQuery);
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,285 @@
|
||||
.ol-box {
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
border: 1.5px solid rgb(179,197,219);
|
||||
background-color: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
.ol-mouse-position {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ol-scale-line {
|
||||
background: rgba(0,60,136,0.3);
|
||||
border-radius: 4px;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
}
|
||||
.ol-scale-line-inner {
|
||||
border: 1px solid #eee;
|
||||
border-top: none;
|
||||
color: #eee;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
margin: 1px;
|
||||
will-change: contents, width;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
.ol-scale-bar {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
}
|
||||
.ol-scale-step-marker {
|
||||
width: 1px;
|
||||
height: 15px;
|
||||
background-color: #000000;
|
||||
float: right;
|
||||
z-index: 10;
|
||||
}
|
||||
.ol-scale-step-text {
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
font-size: 12px;
|
||||
z-index: 11;
|
||||
color: #000000;
|
||||
text-shadow: -2px 0 #FFFFFF, 0 2px #FFFFFF, 2px 0 #FFFFFF, 0 -2px #FFFFFF;
|
||||
}
|
||||
.ol-scale-text {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
bottom: 25px;
|
||||
color: #000000;
|
||||
text-shadow: -2px 0 #FFFFFF, 0 2px #FFFFFF, 2px 0 #FFFFFF, 0 -2px #FFFFFF;
|
||||
}
|
||||
.ol-scale-singlebar {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
z-index: 9;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.ol-unsupported {
|
||||
display: none;
|
||||
}
|
||||
.ol-viewport, .ol-unselectable {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
.ol-viewport canvas {
|
||||
all: unset;
|
||||
}
|
||||
.ol-selectable {
|
||||
-webkit-touch-callout: default;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
.ol-grabbing {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
.ol-grab {
|
||||
cursor: move;
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.ol-control {
|
||||
position: absolute;
|
||||
background-color: rgba(255,255,255,0.4);
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
}
|
||||
.ol-control:hover {
|
||||
background-color: rgba(255,255,255,0.6);
|
||||
}
|
||||
.ol-zoom {
|
||||
top: .5em;
|
||||
left: .5em;
|
||||
}
|
||||
.ol-rotate {
|
||||
top: .5em;
|
||||
right: .5em;
|
||||
transition: opacity .25s linear, visibility 0s linear;
|
||||
}
|
||||
.ol-rotate.ol-hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity .25s linear, visibility 0s linear .25s;
|
||||
}
|
||||
.ol-zoom-extent {
|
||||
top: 4.643em;
|
||||
left: .5em;
|
||||
}
|
||||
.ol-full-screen {
|
||||
right: .5em;
|
||||
top: .5em;
|
||||
}
|
||||
|
||||
.ol-control button {
|
||||
display: block;
|
||||
margin: 1px;
|
||||
padding: 0;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
text-align: center;
|
||||
height: 1.375em;
|
||||
width: 1.375em;
|
||||
line-height: .4em;
|
||||
background-color: rgba(0,60,136,0.5);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.ol-control button::-moz-focus-inner {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
.ol-zoom-extent button {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
.ol-compass {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
font-size: 1.2em;
|
||||
will-change: transform;
|
||||
}
|
||||
.ol-touch .ol-control button {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.ol-touch .ol-zoom-extent {
|
||||
top: 5.5em;
|
||||
}
|
||||
.ol-control button:hover,
|
||||
.ol-control button:focus {
|
||||
text-decoration: none;
|
||||
background-color: rgba(0,60,136,0.7);
|
||||
}
|
||||
.ol-zoom .ol-zoom-in {
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
.ol-zoom .ol-zoom-out {
|
||||
border-radius: 0 0 2px 2px;
|
||||
}
|
||||
|
||||
|
||||
.ol-attribution {
|
||||
text-align: right;
|
||||
bottom: .5em;
|
||||
right: .5em;
|
||||
max-width: calc(100% - 1.3em);
|
||||
display: flex;
|
||||
flex-flow: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
.ol-attribution a {
|
||||
color: rgba(0,60,136,0.7);
|
||||
text-decoration: none;
|
||||
}
|
||||
.ol-attribution ul {
|
||||
margin: 0;
|
||||
padding: 1px .5em;
|
||||
color: #000;
|
||||
text-shadow: 0 0 2px #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
.ol-attribution li {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
}
|
||||
.ol-attribution li:not(:last-child):after {
|
||||
content: " ";
|
||||
}
|
||||
.ol-attribution img {
|
||||
max-height: 2em;
|
||||
max-width: inherit;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ol-attribution button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ol-attribution.ol-collapsed ul {
|
||||
display: none;
|
||||
}
|
||||
.ol-attribution:not(.ol-collapsed) {
|
||||
background: rgba(255,255,255,0.8);
|
||||
}
|
||||
.ol-attribution.ol-uncollapsible {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: 4px 0 0;
|
||||
}
|
||||
.ol-attribution.ol-uncollapsible img {
|
||||
margin-top: -.2em;
|
||||
max-height: 1.6em;
|
||||
}
|
||||
.ol-attribution.ol-uncollapsible button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ol-zoomslider {
|
||||
top: 4.5em;
|
||||
left: .5em;
|
||||
height: 200px;
|
||||
}
|
||||
.ol-zoomslider button {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.ol-touch .ol-zoomslider {
|
||||
top: 5.5em;
|
||||
}
|
||||
|
||||
.ol-overviewmap {
|
||||
left: 0.5em;
|
||||
bottom: 0.5em;
|
||||
}
|
||||
.ol-overviewmap.ol-uncollapsible {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-radius: 0 4px 0 0;
|
||||
}
|
||||
.ol-overviewmap .ol-overviewmap-map,
|
||||
.ol-overviewmap button {
|
||||
display: block;
|
||||
}
|
||||
.ol-overviewmap .ol-overviewmap-map {
|
||||
border: 1px solid #7b98bc;
|
||||
height: 150px;
|
||||
margin: 2px;
|
||||
width: 150px;
|
||||
}
|
||||
.ol-overviewmap:not(.ol-collapsed) button {
|
||||
bottom: 2px;
|
||||
left: 2px;
|
||||
position: absolute;
|
||||
}
|
||||
.ol-overviewmap.ol-collapsed .ol-overviewmap-map,
|
||||
.ol-overviewmap.ol-uncollapsible button {
|
||||
display: none;
|
||||
}
|
||||
.ol-overviewmap:not(.ol-collapsed) {
|
||||
background: rgba(255,255,255,0.8);
|
||||
}
|
||||
.ol-overviewmap-box {
|
||||
border: 2px dotted rgba(0,60,136,0.7);
|
||||
}
|
||||
|
||||
.ol-overviewmap .ol-overviewmap-box:hover {
|
||||
cursor: move;
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// Query cache
|
||||
[
|
||||
'id' => 'Query cache disabled',
|
||||
'name' => __('Query cache disabled'),
|
||||
'formula' => 'query_cache_size',
|
||||
'test' => 'value == 0 || query_cache_type == \'OFF\' || query_cache_type == \'0\'',
|
||||
'issue' => __('The query cache is not enabled.'),
|
||||
'recommendation' => __(
|
||||
'The query cache is known to greatly improve performance if configured correctly. Enable it by'
|
||||
. ' setting {query_cache_size} to a 2 digit MiB value and setting {query_cache_type} to \'ON\'.'
|
||||
. ' <b>Note:</b> If you are using memcached, ignore this recommendation.'
|
||||
),
|
||||
'justification' => __('query_cache_size is set to 0 or query_cache_type is set to \'OFF\''),
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache efficiency (%)',
|
||||
/* xgettext:no-php-format */
|
||||
'name' => __('Query cache efficiency (%)'),
|
||||
'precondition' => 'Com_select + Qcache_hits > 0 && !fired(\'Query cache disabled\')',
|
||||
'formula' => 'Qcache_hits / (Com_select + Qcache_hits) * 100',
|
||||
'test' => 'value < 20',
|
||||
'issue' => __('Query cache not running efficiently, it has a low hit rate.'),
|
||||
'recommendation' => __('Consider increasing {query_cache_limit}.'),
|
||||
'justification' => __('The current query cache hit rate of %s%% is below 20%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query Cache usage',
|
||||
'name' => __('Query Cache usage'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => '100 - Qcache_free_memory / query_cache_size * 100',
|
||||
'test' => 'value < 80',
|
||||
/* xgettext:no-php-format */
|
||||
'issue' => __('Less than 80% of the query cache is being utilized.'),
|
||||
'recommendation' => __(
|
||||
'This might be caused by {query_cache_limit} being too low.'
|
||||
. ' Flushing the query cache might help as well.'
|
||||
),
|
||||
'justification' => __(
|
||||
'The current ratio of free query cache memory to total query'
|
||||
. ' cache size is %s%%. It should be above 80%%'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache fragmentation',
|
||||
'name' => __('Query cache fragmentation'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => 'Qcache_free_blocks / (Qcache_total_blocks / 2) * 100',
|
||||
'test' => 'value > 20',
|
||||
'issue' => __('The query cache is considerably fragmented.'),
|
||||
'recommendation' => __(
|
||||
'Severe fragmentation is likely to (further) increase Qcache_lowmem_prunes. This might be'
|
||||
. ' caused by many Query cache low memory prunes due to {query_cache_size} being too small. For a'
|
||||
. ' immediate but short lived fix you can flush the query cache (might lock the query cache for a'
|
||||
. ' long time). Carefully adjusting {query_cache_min_res_unit} to a lower value might help too,'
|
||||
. ' e.g. you can set it to the average size of your queries in the cache using this formula:'
|
||||
. ' (query_cache_size - qcache_free_memory) / qcache_queries_in_cache'
|
||||
),
|
||||
'justification' => __(
|
||||
'The cache is currently fragmented by %s%% , with 100%% fragmentation meaning that the query'
|
||||
. ' cache is an alternating pattern of free and used blocks. This value should be below 20%%.'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache low memory prunes',
|
||||
'name' => __('Query cache low memory prunes'),
|
||||
'precondition' => 'Qcache_inserts > 0 && !fired(\'Query cache disabled\')',
|
||||
'formula' => 'Qcache_lowmem_prunes / Qcache_inserts * 100',
|
||||
'test' => 'value > 0.1',
|
||||
'issue' => __('Cached queries are removed due to low query cache memory from the query cache.'),
|
||||
'recommendation' => __(
|
||||
'You might want to increase {query_cache_size}, however keep in mind that the overhead of'
|
||||
. ' maintaining the cache is likely to increase with its size, so do this in small increments'
|
||||
. ' and monitor the results.'
|
||||
),
|
||||
'justification' => __(
|
||||
'The ratio of removed queries to inserted queries is %s%%. The lower this value is,'
|
||||
. ' the better (This rules firing limit: 0.1%%)'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache max size',
|
||||
'name' => __('Query cache max size'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => 'query_cache_size',
|
||||
'test' => 'value > 1024 * 1024 * 128',
|
||||
'issue' => __(
|
||||
'The query cache size is above 128 MiB. Big query caches may cause significant'
|
||||
. ' overhead that is required to maintain the cache.'
|
||||
),
|
||||
'recommendation' => __(
|
||||
'Depending on your environment, it might be performance increasing to reduce this value.'
|
||||
),
|
||||
'justification' => __('Current query cache size: %s'),
|
||||
'justification_formula' => 'ADVISOR_formatByteDown(value, 2, 2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache min result size',
|
||||
'name' => __('Query cache min result size'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => 'query_cache_limit',
|
||||
'test' => 'value == 1024*1024',
|
||||
'issue' => __('The max size of the result set in the query cache is the default of 1 MiB.'),
|
||||
'recommendation' => __(
|
||||
'Changing {query_cache_limit} (usually by increasing) may increase efficiency. This variable'
|
||||
. ' determines the maximum size a query result may have to be inserted into the query cache.'
|
||||
. ' If there are many query results above 1 MiB that are well cacheable (many reads, little writes)'
|
||||
. ' then increasing {query_cache_limit} will increase efficiency. Whereas in the case of many query'
|
||||
. ' results being above 1 MiB that are not very well cacheable (often invalidated due to table'
|
||||
. ' updates) increasing {query_cache_limit} might reduce efficiency.'
|
||||
),
|
||||
'justification' => __('query_cache_limit is set to 1 MiB'),
|
||||
],
|
||||
];
|
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Command;
|
||||
|
||||
use PhpMyAdmin\Template;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Twig\Error\Error;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Source;
|
||||
|
||||
use function array_push;
|
||||
use function closedir;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function file_get_contents;
|
||||
use function is_dir;
|
||||
use function is_file;
|
||||
use function max;
|
||||
use function min;
|
||||
use function opendir;
|
||||
use function preg_match;
|
||||
use function readdir;
|
||||
use function restore_error_handler;
|
||||
use function set_error_handler;
|
||||
use function sprintf;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const E_USER_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Command that will validate your template syntax and output encountered errors.
|
||||
* Author: Marc Weistroff <marc.weistroff@sensiolabs.com>
|
||||
* Author: Jérôme Tamarelle <jerome@tamarelle.net>
|
||||
*
|
||||
* Copyright (c) 2013-2021 Fabien Potencier
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
class TwigLintCommand extends Command
|
||||
{
|
||||
/** @var string|null */
|
||||
protected static $defaultName = 'lint:twig';
|
||||
|
||||
/** @var string|null */
|
||||
protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors';
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription((string) self::$defaultDescription)
|
||||
->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors');
|
||||
}
|
||||
|
||||
protected function findFiles(string $baseFolder): array
|
||||
{
|
||||
/* Open the handle */
|
||||
$handle = @opendir($baseFolder);
|
||||
if ($handle === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$foundFiles = [];
|
||||
|
||||
while (($file = readdir($handle)) !== false) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemPath = $baseFolder . DIRECTORY_SEPARATOR . $file;
|
||||
|
||||
if (is_dir($itemPath)) {
|
||||
array_push($foundFiles, ...$this->findFiles($itemPath));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_file($itemPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$foundFiles[] = $itemPath;
|
||||
}
|
||||
|
||||
/* Close the handle */
|
||||
closedir($handle);
|
||||
|
||||
return $foundFiles;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$showDeprecations = $input->getOption('show-deprecations');
|
||||
|
||||
if ($showDeprecations) {
|
||||
$prevErrorHandler = set_error_handler(
|
||||
static function (int $level, string $message, string $file, int $line) use (&$prevErrorHandler) {
|
||||
if ($level === E_USER_DEPRECATED) {
|
||||
$templateLine = 0;
|
||||
if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) {
|
||||
$templateLine = (int) $matches[1];
|
||||
}
|
||||
|
||||
throw new Error($message, $templateLine);
|
||||
}
|
||||
|
||||
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$filesInfo = $this->getFilesInfo(ROOT_PATH . 'templates');
|
||||
} finally {
|
||||
if ($showDeprecations) {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->display($output, $io, $filesInfo);
|
||||
}
|
||||
|
||||
protected function getFilesInfo(string $templatesPath): array
|
||||
{
|
||||
$filesInfo = [];
|
||||
$filesFound = $this->findFiles($templatesPath);
|
||||
foreach ($filesFound as $file) {
|
||||
$filesInfo[] = $this->validate($this->getTemplateContents($file), $file);
|
||||
}
|
||||
|
||||
return $filesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows easier testing
|
||||
*/
|
||||
protected function getTemplateContents(string $filePath): string
|
||||
{
|
||||
return (string) file_get_contents($filePath);
|
||||
}
|
||||
|
||||
private function validate(string $template, string $file): array
|
||||
{
|
||||
$twig = Template::getTwigEnvironment(null);
|
||||
|
||||
$realLoader = $twig->getLoader();
|
||||
try {
|
||||
$temporaryLoader = new ArrayLoader([$file => $template]);
|
||||
$twig->setLoader($temporaryLoader);
|
||||
$nodeTree = $twig->parse($twig->tokenize(new Source($template, $file)));
|
||||
$twig->compile($nodeTree);
|
||||
$twig->setLoader($realLoader);
|
||||
} catch (Error $e) {
|
||||
$twig->setLoader($realLoader);
|
||||
|
||||
return [
|
||||
'template' => $template,
|
||||
'file' => $file,
|
||||
'line' => $e->getTemplateLine(),
|
||||
'valid' => false,
|
||||
'exception' => $e,
|
||||
];
|
||||
}
|
||||
|
||||
return ['template' => $template, 'file' => $file, 'valid' => true];
|
||||
}
|
||||
|
||||
private function display(OutputInterface $output, SymfonyStyle $io, array $filesInfo): int
|
||||
{
|
||||
$errors = 0;
|
||||
|
||||
foreach ($filesInfo as $info) {
|
||||
if ($info['valid'] && $output->isVerbose()) {
|
||||
$io->comment('<info>OK</info>' . ($info['file'] ? sprintf(' in %s', $info['file']) : ''));
|
||||
} elseif (! $info['valid']) {
|
||||
++$errors;
|
||||
$this->renderException($io, $info['template'], $info['exception'], $info['file']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors === 0) {
|
||||
$io->success(sprintf('All %d Twig files contain valid syntax.', count($filesInfo)));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->warning(
|
||||
sprintf(
|
||||
'%d Twig files have valid syntax and %d contain errors.',
|
||||
count($filesInfo) - $errors,
|
||||
$errors
|
||||
)
|
||||
);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
private function renderException(
|
||||
SymfonyStyle $output,
|
||||
string $template,
|
||||
Error $exception,
|
||||
?string $file = null
|
||||
): void {
|
||||
$line = $exception->getTemplateLine();
|
||||
|
||||
if ($file) {
|
||||
$output->text(sprintf('<error> ERROR </error> in %s (line %s)', $file, $line));
|
||||
} else {
|
||||
$output->text(sprintf('<error> ERROR </error> (line %s)', $line));
|
||||
}
|
||||
|
||||
// If the line is not known (this might happen for deprecations if we fail at detecting the line for instance),
|
||||
// we render the message without context, to ensure the message is displayed.
|
||||
if ($line <= 0) {
|
||||
$output->text(sprintf('<error> >> %s</error> ', $exception->getRawMessage()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->getContext($template, $line) as $lineNumber => $code) {
|
||||
$output->text(sprintf(
|
||||
'%s %-6s %s',
|
||||
$lineNumber === $line ? '<error> >> </error>' : ' ',
|
||||
$lineNumber,
|
||||
$code
|
||||
));
|
||||
if ($lineNumber !== $line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output->text(sprintf('<error> >> %s</error> ', $exception->getRawMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private function getContext(string $template, int $line, int $context = 3): array
|
||||
{
|
||||
$lines = explode("\n", $template);
|
||||
|
||||
$position = max(0, $line - $context);
|
||||
$max = min(count($lines), $line - 1 + $context);
|
||||
|
||||
$result = [];
|
||||
while ($position < $max) {
|
||||
$result[$position + 1] = $lines[$position];
|
||||
++$position;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -0,0 +1,889 @@
|
||||
<?php
|
||||
/**
|
||||
* Form management class, displays and processes forms
|
||||
*
|
||||
* Explanation of used terms:
|
||||
* o work_path - original field path, eg. Servers/4/verbose
|
||||
* o system_path - work_path modified so that it points to the first server,
|
||||
* eg. Servers/1/verbose
|
||||
* o translated_path - work_path modified for HTML field name, a path with
|
||||
* slashes changed to hyphens, eg. Servers-4-verbose
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\User\UserFormList;
|
||||
use PhpMyAdmin\Html\MySQLDocumentation;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
use function __;
|
||||
use function array_flip;
|
||||
use function array_keys;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function function_exists;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_numeric;
|
||||
use function mb_substr;
|
||||
use function preg_match;
|
||||
use function settype;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function trigger_error;
|
||||
use function trim;
|
||||
|
||||
use const E_USER_WARNING;
|
||||
|
||||
/**
|
||||
* Form management class, displays and processes forms
|
||||
*/
|
||||
class FormDisplay
|
||||
{
|
||||
/**
|
||||
* ConfigFile instance
|
||||
*
|
||||
* @var ConfigFile
|
||||
*/
|
||||
private $configFile;
|
||||
|
||||
/**
|
||||
* Form list
|
||||
*
|
||||
* @var Form[]
|
||||
*/
|
||||
private $forms = [];
|
||||
|
||||
/**
|
||||
* Stores validation errors, indexed by paths
|
||||
* [ Form_name ] is an array of form errors
|
||||
* [path] is a string storing error associated with single field
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $errors = [];
|
||||
|
||||
/**
|
||||
* Paths changed so that they can be used as HTML ids, indexed by paths
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $translatedPaths = [];
|
||||
|
||||
/**
|
||||
* Server paths change indexes so we define maps from current server
|
||||
* path to the first one, indexed by work path
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $systemPaths = [];
|
||||
|
||||
/**
|
||||
* Tells whether forms have been validated
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isValidated = true;
|
||||
|
||||
/**
|
||||
* Dictionary with user preferences keys
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $userprefsKeys;
|
||||
|
||||
/**
|
||||
* Dictionary with disallowed user preferences keys
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $userprefsDisallow;
|
||||
|
||||
/** @var FormDisplayTemplate */
|
||||
private $formDisplayTemplate;
|
||||
|
||||
/**
|
||||
* @param ConfigFile $cf Config file instance
|
||||
*/
|
||||
public function __construct(ConfigFile $cf)
|
||||
{
|
||||
$this->formDisplayTemplate = new FormDisplayTemplate($GLOBALS['config']);
|
||||
$this->configFile = $cf;
|
||||
// initialize validators
|
||||
Validator::getValidators($this->configFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link ConfigFile} associated with this instance
|
||||
*
|
||||
* @return ConfigFile
|
||||
*/
|
||||
public function getConfigFile()
|
||||
{
|
||||
return $this->configFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers form in form manager
|
||||
*
|
||||
* @param string $formName Form name
|
||||
* @param array $form Form data
|
||||
* @param int $serverId 0 if new server, validation; >= 1 if editing a server
|
||||
*/
|
||||
public function registerForm($formName, array $form, $serverId = null): void
|
||||
{
|
||||
$this->forms[$formName] = new Form($formName, $form, $this->configFile, $serverId);
|
||||
$this->isValidated = false;
|
||||
foreach ($this->forms[$formName]->fields as $path) {
|
||||
$workPath = $serverId === null
|
||||
? $path
|
||||
: str_replace('Servers/1/', 'Servers/' . $serverId . '/', $path);
|
||||
$this->systemPaths[$workPath] = $path;
|
||||
$this->translatedPaths[$workPath] = str_replace('/', '-', $workPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes forms, returns true on successful save
|
||||
*
|
||||
* @param bool $allowPartialSave allows for partial form saving
|
||||
* on failed validation
|
||||
* @param bool $checkFormSubmit whether check for $_POST['submit_save']
|
||||
*/
|
||||
public function process($allowPartialSave = true, $checkFormSubmit = true): bool
|
||||
{
|
||||
if ($checkFormSubmit && ! isset($_POST['submit_save'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// save forms
|
||||
if (count($this->forms) > 0) {
|
||||
return $this->save(array_keys($this->forms), $allowPartialSave);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs validation for all registered forms
|
||||
*/
|
||||
private function validate(): void
|
||||
{
|
||||
if ($this->isValidated) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paths = [];
|
||||
$values = [];
|
||||
foreach ($this->forms as $form) {
|
||||
$paths[] = $form->name;
|
||||
// collect values and paths
|
||||
foreach ($form->fields as $path) {
|
||||
$workPath = array_search($path, $this->systemPaths);
|
||||
$values[$path] = $this->configFile->getValue($workPath);
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// run validation
|
||||
$errors = Validator::validate($this->configFile, $paths, $values, false);
|
||||
|
||||
// change error keys from canonical paths to work paths
|
||||
if (is_array($errors) && count($errors) > 0) {
|
||||
$this->errors = [];
|
||||
foreach ($errors as $path => $errorList) {
|
||||
$workPath = array_search($path, $this->systemPaths);
|
||||
// field error
|
||||
if (! $workPath) {
|
||||
// form error, fix path
|
||||
$workPath = $path;
|
||||
}
|
||||
|
||||
$this->errors[$workPath] = $errorList;
|
||||
}
|
||||
}
|
||||
|
||||
$this->isValidated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs HTML for forms
|
||||
*
|
||||
* @param bool $showButtons whether show submit and reset button
|
||||
* @param string $formAction action attribute for the form
|
||||
* @param array|null $hiddenFields array of form hidden fields (key: field
|
||||
* name)
|
||||
*
|
||||
* @return string HTML for forms
|
||||
*/
|
||||
public function getDisplay(
|
||||
$showButtons = true,
|
||||
$formAction = null,
|
||||
$hiddenFields = null
|
||||
) {
|
||||
$js = [];
|
||||
$jsDefault = [];
|
||||
|
||||
/**
|
||||
* We do validation on page refresh when browser remembers field values,
|
||||
* add a field with known value which will be used for checks.
|
||||
*/
|
||||
static $hasCheckPageRefresh = false;
|
||||
if (! $hasCheckPageRefresh) {
|
||||
$hasCheckPageRefresh = true;
|
||||
}
|
||||
|
||||
$tabs = [];
|
||||
foreach ($this->forms as $form) {
|
||||
$tabs[$form->name] = Descriptions::get('Form_' . $form->name);
|
||||
}
|
||||
|
||||
// validate only when we aren't displaying a "new server" form
|
||||
$isNewServer = false;
|
||||
foreach ($this->forms as $form) {
|
||||
if ($form->index === 0) {
|
||||
$isNewServer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $isNewServer) {
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
// user preferences
|
||||
$this->loadUserprefsInfo();
|
||||
|
||||
$validators = Validator::getValidators($this->configFile);
|
||||
$forms = [];
|
||||
|
||||
foreach ($this->forms as $key => $form) {
|
||||
$this->formDisplayTemplate->group = 0;
|
||||
$forms[$key] = [
|
||||
'name' => $form->name,
|
||||
'descriptions' => [
|
||||
'name' => Descriptions::get('Form_' . $form->name, 'name'),
|
||||
'desc' => Descriptions::get('Form_' . $form->name, 'desc'),
|
||||
],
|
||||
'errors' => $this->errors[$form->name] ?? null,
|
||||
'fields_html' => '',
|
||||
];
|
||||
|
||||
foreach ($form->fields as $field => $path) {
|
||||
$workPath = array_search($path, $this->systemPaths);
|
||||
$translatedPath = $this->translatedPaths[$workPath];
|
||||
// always true/false for user preferences display
|
||||
// otherwise null
|
||||
$userPrefsAllow = isset($this->userprefsKeys[$path])
|
||||
? ! isset($this->userprefsDisallow[$path])
|
||||
: null;
|
||||
// display input
|
||||
$forms[$key]['fields_html'] .= $this->displayFieldInput(
|
||||
$form,
|
||||
$field,
|
||||
$path,
|
||||
$workPath,
|
||||
$translatedPath,
|
||||
true,
|
||||
$userPrefsAllow,
|
||||
$jsDefault
|
||||
);
|
||||
// register JS validators for this field
|
||||
if (! isset($validators[$path])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->formDisplayTemplate->addJsValidate($translatedPath, $validators[$path], $js);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->formDisplayTemplate->display([
|
||||
'action' => $formAction,
|
||||
'has_check_page_refresh' => $hasCheckPageRefresh,
|
||||
'hidden_fields' => (array) $hiddenFields,
|
||||
'tabs' => $tabs,
|
||||
'forms' => $forms,
|
||||
'show_buttons' => $showButtons,
|
||||
'js_array' => $js,
|
||||
'js_default' => $jsDefault,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares data for input field display and outputs HTML code
|
||||
*
|
||||
* @param Form $form Form object
|
||||
* @param string $field field name as it appears in $form
|
||||
* @param string $systemPath field path, eg. Servers/1/verbose
|
||||
* @param string $workPath work path, eg. Servers/4/verbose
|
||||
* @param string $translatedPath work path changed so that it can be
|
||||
* used as XHTML id
|
||||
* @param bool $showRestoreDefault whether show "restore default" button
|
||||
* besides the input field
|
||||
* @param bool|null $userPrefsAllow whether user preferences are enabled
|
||||
* for this field (null - no support,
|
||||
* true/false - enabled/disabled)
|
||||
* @param array $jsDefault array which stores JavaScript code
|
||||
* to be displayed
|
||||
*
|
||||
* @return string|null HTML for input field
|
||||
*/
|
||||
private function displayFieldInput(
|
||||
Form $form,
|
||||
$field,
|
||||
$systemPath,
|
||||
$workPath,
|
||||
$translatedPath,
|
||||
$showRestoreDefault,
|
||||
$userPrefsAllow,
|
||||
array &$jsDefault
|
||||
) {
|
||||
$name = Descriptions::get($systemPath);
|
||||
$description = Descriptions::get($systemPath, 'desc');
|
||||
|
||||
$value = $this->configFile->get($workPath);
|
||||
$valueDefault = $this->configFile->getDefault($systemPath);
|
||||
$valueIsDefault = false;
|
||||
if ($value === null || $value === $valueDefault) {
|
||||
$value = $valueDefault;
|
||||
$valueIsDefault = true;
|
||||
}
|
||||
|
||||
$opts = [
|
||||
'doc' => $this->getDocLink($systemPath),
|
||||
'show_restore_default' => $showRestoreDefault,
|
||||
'userprefs_allow' => $userPrefsAllow,
|
||||
'userprefs_comment' => Descriptions::get($systemPath, 'cmt'),
|
||||
];
|
||||
if (isset($form->default[$systemPath])) {
|
||||
$opts['setvalue'] = (string) $form->default[$systemPath];
|
||||
}
|
||||
|
||||
if (isset($this->errors[$workPath])) {
|
||||
$opts['errors'] = $this->errors[$workPath];
|
||||
}
|
||||
|
||||
$type = '';
|
||||
switch ($form->getOptionType($field)) {
|
||||
case 'string':
|
||||
$type = 'text';
|
||||
break;
|
||||
case 'short_string':
|
||||
$type = 'short_text';
|
||||
break;
|
||||
case 'double':
|
||||
case 'integer':
|
||||
$type = 'number_text';
|
||||
break;
|
||||
case 'boolean':
|
||||
$type = 'checkbox';
|
||||
break;
|
||||
case 'select':
|
||||
$type = 'select';
|
||||
$opts['values'] = $form->getOptionValueList($form->fields[$field]);
|
||||
break;
|
||||
case 'array':
|
||||
$type = 'list';
|
||||
$value = (array) $value;
|
||||
$valueDefault = (array) $valueDefault;
|
||||
break;
|
||||
case 'group':
|
||||
// :group:end is changed to :group:end:{unique id} in Form class
|
||||
$htmlOutput = '';
|
||||
if (mb_substr($field, 7, 4) !== 'end:') {
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayGroupHeader(
|
||||
mb_substr($field, 7)
|
||||
);
|
||||
} else {
|
||||
$this->formDisplayTemplate->displayGroupFooter();
|
||||
}
|
||||
|
||||
return $htmlOutput;
|
||||
|
||||
case 'NULL':
|
||||
trigger_error('Field ' . $systemPath . ' has no type', E_USER_WARNING);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// detect password fields
|
||||
if (
|
||||
$type === 'text'
|
||||
&& (mb_substr($translatedPath, -9) === '-password'
|
||||
|| mb_substr($translatedPath, -4) === 'pass'
|
||||
|| mb_substr($translatedPath, -4) === 'Pass')
|
||||
) {
|
||||
$type = 'password';
|
||||
}
|
||||
|
||||
// TrustedProxies requires changes before displaying
|
||||
if ($systemPath === 'TrustedProxies') {
|
||||
foreach ($value as $ip => &$v) {
|
||||
if (preg_match('/^-\d+$/', $ip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$v = $ip . ': ' . $v;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setComments($systemPath, $opts);
|
||||
|
||||
// send default value to form's JS
|
||||
$jsLine = '\'' . $translatedPath . '\': ';
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
case 'short_text':
|
||||
case 'number_text':
|
||||
case 'password':
|
||||
$jsLine .= '\'' . Sanitize::escapeJsString($valueDefault) . '\'';
|
||||
break;
|
||||
case 'checkbox':
|
||||
$jsLine .= $valueDefault ? 'true' : 'false';
|
||||
break;
|
||||
case 'select':
|
||||
$valueDefaultJs = is_bool($valueDefault)
|
||||
? (int) $valueDefault
|
||||
: $valueDefault;
|
||||
$jsLine .= '[\'' . Sanitize::escapeJsString($valueDefaultJs) . '\']';
|
||||
break;
|
||||
case 'list':
|
||||
$val = $valueDefault;
|
||||
if (isset($val['wrapper_params'])) {
|
||||
unset($val['wrapper_params']);
|
||||
}
|
||||
|
||||
$jsLine .= '\'' . Sanitize::escapeJsString(implode("\n", $val))
|
||||
. '\'';
|
||||
break;
|
||||
}
|
||||
|
||||
$jsDefault[] = $jsLine;
|
||||
|
||||
return $this->formDisplayTemplate->displayInput(
|
||||
$translatedPath,
|
||||
$name,
|
||||
$type,
|
||||
$value,
|
||||
$description,
|
||||
$valueIsDefault,
|
||||
$opts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays errors
|
||||
*
|
||||
* @return string|null HTML for errors
|
||||
*/
|
||||
public function displayErrors()
|
||||
{
|
||||
$this->validate();
|
||||
if (count($this->errors) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$htmlOutput = '';
|
||||
|
||||
foreach ($this->errors as $systemPath => $errorList) {
|
||||
if (isset($this->systemPaths[$systemPath])) {
|
||||
$name = Descriptions::get($this->systemPaths[$systemPath]);
|
||||
} else {
|
||||
$name = Descriptions::get('Form_' . $systemPath);
|
||||
}
|
||||
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayErrors($name, $errorList);
|
||||
}
|
||||
|
||||
return $htmlOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts erroneous fields to their default values
|
||||
*/
|
||||
public function fixErrors(): void
|
||||
{
|
||||
$this->validate();
|
||||
if (count($this->errors) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cf = $this->configFile;
|
||||
foreach (array_keys($this->errors) as $workPath) {
|
||||
if (! isset($this->systemPaths[$workPath])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$canonicalPath = $this->systemPaths[$workPath];
|
||||
$cf->set($workPath, $cf->getDefault($canonicalPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates select field and casts $value to correct type
|
||||
*
|
||||
* @param string|bool $value Current value
|
||||
* @param array $allowed List of allowed values
|
||||
*/
|
||||
private function validateSelect(&$value, array $allowed): bool
|
||||
{
|
||||
$valueCmp = is_bool($value)
|
||||
? (int) $value
|
||||
: $value;
|
||||
foreach (array_keys($allowed) as $vk) {
|
||||
// equality comparison only if both values are numeric or not numeric
|
||||
// (allows to skip 0 == 'string' equalling to true)
|
||||
// or identity (for string-string)
|
||||
if (! (($vk == $value && ! (is_numeric($valueCmp) xor is_numeric($vk))) || $vk === $value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep boolean value as boolean
|
||||
if (! is_bool($value)) {
|
||||
// phpcs:ignore Generic.PHP.ForbiddenFunctions
|
||||
settype($value, gettype($vk));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and saves form data to session
|
||||
*
|
||||
* @param array|string $forms array of form names
|
||||
* @param bool $allowPartialSave allows for partial form saving on
|
||||
* failed validation
|
||||
*/
|
||||
public function save($forms, $allowPartialSave = true): bool
|
||||
{
|
||||
$result = true;
|
||||
$forms = (array) $forms;
|
||||
|
||||
$values = [];
|
||||
$toSave = [];
|
||||
$isSetupScript = $GLOBALS['config']->get('is_setup');
|
||||
if ($isSetupScript) {
|
||||
$this->loadUserprefsInfo();
|
||||
}
|
||||
|
||||
$this->errors = [];
|
||||
foreach ($forms as $formName) {
|
||||
if (! isset($this->forms[$formName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$form = $this->forms[$formName];
|
||||
// get current server id
|
||||
$changeIndex = $form->index === 0
|
||||
? $this->configFile->getServerCount() + 1
|
||||
: false;
|
||||
// grab POST values
|
||||
foreach ($form->fields as $field => $systemPath) {
|
||||
$workPath = array_search($systemPath, $this->systemPaths);
|
||||
$key = $this->translatedPaths[$workPath];
|
||||
$type = (string) $form->getOptionType($field);
|
||||
|
||||
// skip groups
|
||||
if ($type === 'group') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ensure the value is set
|
||||
if (! isset($_POST[$key])) {
|
||||
// checkboxes aren't set by browsers if they're off
|
||||
if ($type !== 'boolean') {
|
||||
$this->errors[$form->name][] = sprintf(
|
||||
__('Missing data for %s'),
|
||||
'<i>' . Descriptions::get($systemPath) . '</i>'
|
||||
);
|
||||
$result = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$_POST[$key] = false;
|
||||
}
|
||||
|
||||
// user preferences allow/disallow
|
||||
if ($isSetupScript && isset($this->userprefsKeys[$systemPath])) {
|
||||
if (isset($this->userprefsDisallow[$systemPath], $_POST[$key . '-userprefs-allow'])) {
|
||||
unset($this->userprefsDisallow[$systemPath]);
|
||||
} elseif (! isset($_POST[$key . '-userprefs-allow'])) {
|
||||
$this->userprefsDisallow[$systemPath] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// cast variables to correct type
|
||||
switch ($type) {
|
||||
case 'double':
|
||||
$_POST[$key] = Util::requestString($_POST[$key]);
|
||||
// phpcs:ignore Generic.PHP.ForbiddenFunctions
|
||||
settype($_POST[$key], 'float');
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'integer':
|
||||
if ($_POST[$key] !== '') {
|
||||
$_POST[$key] = Util::requestString($_POST[$key]);
|
||||
// phpcs:ignore Generic.PHP.ForbiddenFunctions
|
||||
settype($_POST[$key], $type);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'select':
|
||||
$successfullyValidated = $this->validateSelect(
|
||||
$_POST[$key],
|
||||
$form->getOptionValueList($systemPath)
|
||||
);
|
||||
if (! $successfullyValidated) {
|
||||
$this->errors[$workPath][] = __('Incorrect value!');
|
||||
$result = false;
|
||||
// "continue" for the $form->fields foreach-loop
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'string':
|
||||
case 'short_string':
|
||||
$_POST[$key] = Util::requestString($_POST[$key]);
|
||||
break;
|
||||
case 'array':
|
||||
// eliminate empty values and ensure we have an array
|
||||
$postValues = is_array($_POST[$key])
|
||||
? $_POST[$key]
|
||||
: explode("\n", $_POST[$key]);
|
||||
$_POST[$key] = [];
|
||||
$this->fillPostArrayParameters($postValues, $key);
|
||||
break;
|
||||
}
|
||||
|
||||
// now we have value with proper type
|
||||
$values[$systemPath] = $_POST[$key];
|
||||
if ($changeIndex !== false) {
|
||||
$workPath = str_replace(
|
||||
'Servers/' . $form->index . '/',
|
||||
'Servers/' . $changeIndex . '/',
|
||||
$workPath
|
||||
);
|
||||
}
|
||||
|
||||
$toSave[$workPath] = $systemPath;
|
||||
}
|
||||
}
|
||||
|
||||
// save forms
|
||||
if (! $allowPartialSave && ! empty($this->errors)) {
|
||||
// don't look for non-critical errors
|
||||
$this->validate();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($toSave as $workPath => $path) {
|
||||
// TrustedProxies requires changes before saving
|
||||
if ($path === 'TrustedProxies') {
|
||||
$proxies = [];
|
||||
$i = 0;
|
||||
foreach ($values[$path] as $value) {
|
||||
$matches = [];
|
||||
$match = preg_match('/^(.+):(?:[ ]?)(\\w+)$/', $value, $matches);
|
||||
if ($match) {
|
||||
// correct 'IP: HTTP header' pair
|
||||
$ip = trim($matches[1]);
|
||||
$proxies[$ip] = trim($matches[2]);
|
||||
} else {
|
||||
// save also incorrect values
|
||||
$proxies['-' . $i] = $value;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
$values[$path] = $proxies;
|
||||
}
|
||||
|
||||
$this->configFile->set($workPath, $values[$path], $path);
|
||||
}
|
||||
|
||||
if ($isSetupScript) {
|
||||
$this->configFile->set(
|
||||
'UserprefsDisallow',
|
||||
array_keys($this->userprefsDisallow)
|
||||
);
|
||||
}
|
||||
|
||||
// don't look for non-critical errors
|
||||
$this->validate();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether form validation failed
|
||||
*/
|
||||
public function hasErrors(): bool
|
||||
{
|
||||
return count($this->errors) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns link to documentation
|
||||
*
|
||||
* @param string $path Path to documentation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDocLink($path)
|
||||
{
|
||||
$test = mb_substr($path, 0, 6);
|
||||
if ($test === 'Import' || $test === 'Export') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return MySQLDocumentation::getDocumentationLink(
|
||||
'config',
|
||||
'cfg_' . $this->getOptName($path),
|
||||
Sanitize::isSetup() ? '../' : './'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes path so it can be used in URLs
|
||||
*
|
||||
* @param string $path Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getOptName($path)
|
||||
{
|
||||
return str_replace(['Servers/1/', '/'], ['Servers/', '_'], $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills out {@link userprefs_keys} and {@link userprefs_disallow}
|
||||
*/
|
||||
private function loadUserprefsInfo(): void
|
||||
{
|
||||
if ($this->userprefsKeys !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->userprefsKeys = array_flip(UserFormList::getFields());
|
||||
// read real config for user preferences display
|
||||
$userPrefsDisallow = $GLOBALS['config']->get('is_setup')
|
||||
? $this->configFile->get('UserprefsDisallow', [])
|
||||
: $GLOBALS['cfg']['UserprefsDisallow'];
|
||||
$this->userprefsDisallow = array_flip($userPrefsDisallow ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets field comments and warnings based on current environment
|
||||
*
|
||||
* @param string $systemPath Path to settings
|
||||
* @param array $opts Chosen options
|
||||
*/
|
||||
private function setComments($systemPath, array &$opts): void
|
||||
{
|
||||
// RecodingEngine - mark unavailable types
|
||||
if ($systemPath === 'RecodingEngine') {
|
||||
$comment = '';
|
||||
if (! function_exists('iconv')) {
|
||||
$opts['values']['iconv'] .= ' (' . __('unavailable') . ')';
|
||||
$comment = sprintf(
|
||||
__('"%s" requires %s extension'),
|
||||
'iconv',
|
||||
'iconv'
|
||||
);
|
||||
}
|
||||
|
||||
if (! function_exists('recode_string')) {
|
||||
$opts['values']['recode'] .= ' (' . __('unavailable') . ')';
|
||||
$comment .= ($comment ? ', ' : '') . sprintf(
|
||||
__('"%s" requires %s extension'),
|
||||
'recode',
|
||||
'recode'
|
||||
);
|
||||
}
|
||||
|
||||
/* mbstring is always there thanks to polyfill */
|
||||
$opts['comment'] = $comment;
|
||||
$opts['comment_warning'] = true;
|
||||
}
|
||||
|
||||
// ZipDump, GZipDump, BZipDump - check function availability
|
||||
if ($systemPath === 'ZipDump' || $systemPath === 'GZipDump' || $systemPath === 'BZipDump') {
|
||||
$comment = '';
|
||||
$funcs = [
|
||||
'ZipDump' => [
|
||||
'zip_open',
|
||||
'gzcompress',
|
||||
],
|
||||
'GZipDump' => [
|
||||
'gzopen',
|
||||
'gzencode',
|
||||
],
|
||||
'BZipDump' => [
|
||||
'bzopen',
|
||||
'bzcompress',
|
||||
],
|
||||
];
|
||||
if (! function_exists($funcs[$systemPath][0])) {
|
||||
$comment = sprintf(
|
||||
__(
|
||||
'Compressed import will not work due to missing function %s.'
|
||||
),
|
||||
$funcs[$systemPath][0]
|
||||
);
|
||||
}
|
||||
|
||||
if (! function_exists($funcs[$systemPath][1])) {
|
||||
$comment .= ($comment ? '; ' : '') . sprintf(
|
||||
__(
|
||||
'Compressed export will not work due to missing function %s.'
|
||||
),
|
||||
$funcs[$systemPath][1]
|
||||
);
|
||||
}
|
||||
|
||||
$opts['comment'] = $comment;
|
||||
$opts['comment_warning'] = true;
|
||||
}
|
||||
|
||||
if ($GLOBALS['config']->get('is_setup')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($systemPath !== 'MaxDbList' && $systemPath !== 'MaxTableList' && $systemPath !== 'QueryHistoryMax') {
|
||||
return;
|
||||
}
|
||||
|
||||
$opts['comment'] = sprintf(
|
||||
__('maximum %s'),
|
||||
$GLOBALS['cfg'][$systemPath]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy items of an array to $_POST variable
|
||||
*
|
||||
* @param array $postValues List of parameters
|
||||
* @param string $key Array key
|
||||
*/
|
||||
private function fillPostArrayParameters(array $postValues, $key): void
|
||||
{
|
||||
foreach ($postValues as $v) {
|
||||
$v = Util::requestString($v);
|
||||
if ($v === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_POST[$key][] = $v;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
/**
|
||||
* Form templates
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
use function array_shift;
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
* PhpMyAdmin\Config\FormDisplayTemplate class
|
||||
*/
|
||||
class FormDisplayTemplate
|
||||
{
|
||||
/** @var int */
|
||||
public $group;
|
||||
|
||||
/** @var Config */
|
||||
protected $config;
|
||||
|
||||
/** @var Template */
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* @param Config $config Config instance
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->template = new Template();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays input field
|
||||
*
|
||||
* $opts keys:
|
||||
* o doc - (string) documentation link
|
||||
* o errors - error array
|
||||
* o setvalue - (string) shows button allowing to set predefined value
|
||||
* o show_restore_default - (boolean) whether show "restore default" button
|
||||
* o userprefs_allow - whether user preferences are enabled for this field
|
||||
* (null - no support, true/false - enabled/disabled)
|
||||
* o userprefs_comment - (string) field comment
|
||||
* o values - key - value pairs for <select> fields
|
||||
* o values_escaped - (boolean) tells whether values array is already escaped
|
||||
* (defaults to false)
|
||||
* o values_disabled - (array)list of disabled values (keys from values)
|
||||
* o comment - (string) tooltip comment
|
||||
* o comment_warning - (bool) whether this comments warns about something
|
||||
*
|
||||
* @param string $path config option path
|
||||
* @param string $name config option name
|
||||
* @param string $type type of config option
|
||||
* @param mixed $value current value
|
||||
* @param string $description verbose description
|
||||
* @param bool $valueIsDefault whether value is default
|
||||
* @param array|null $opts see above description
|
||||
*/
|
||||
public function displayInput(
|
||||
$path,
|
||||
$name,
|
||||
$type,
|
||||
$value,
|
||||
$description = '',
|
||||
$valueIsDefault = true,
|
||||
$opts = null
|
||||
): string {
|
||||
$isSetupScript = $this->config->get('is_setup');
|
||||
$optionIsDisabled = ! $isSetupScript && isset($opts['userprefs_allow']) && ! $opts['userprefs_allow'];
|
||||
$trClass = $this->group > 0 ? 'group-field group-field-' . $this->group : '';
|
||||
if (isset($opts['setvalue']) && $opts['setvalue'] === ':group') {
|
||||
unset($opts['setvalue']);
|
||||
$this->group++;
|
||||
$trClass = 'group-header-field group-header-' . $this->group;
|
||||
}
|
||||
|
||||
return $this->template->render('config/form_display/input', [
|
||||
'is_setup' => $isSetupScript,
|
||||
'allows_customization' => $opts['userprefs_allow'] ?? null,
|
||||
'path' => $path,
|
||||
'has_errors' => isset($opts['errors']) && ! empty($opts['errors']),
|
||||
'errors' => $opts['errors'] ?? [],
|
||||
'show_restore_default' => $opts['show_restore_default'] ?? null,
|
||||
'set_value' => $opts['setvalue'] ?? null,
|
||||
'tr_class' => $trClass,
|
||||
'name' => $name,
|
||||
'doc' => $opts['doc'] ?? '',
|
||||
'option_is_disabled' => $optionIsDisabled,
|
||||
'description' => $description,
|
||||
'comment' => $opts['userprefs_comment'] ?? null,
|
||||
'type' => $type,
|
||||
'value' => $value,
|
||||
'value_is_default' => $valueIsDefault,
|
||||
'select_values' => $opts['values'] ?? [],
|
||||
'select_values_disabled' => $opts['values_disabled'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display group header
|
||||
*
|
||||
* @param string $headerText Text of header
|
||||
*/
|
||||
public function displayGroupHeader(string $headerText): string
|
||||
{
|
||||
$this->group++;
|
||||
if ($headerText === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$colspan = $this->config->get('is_setup') ? 3 : 2;
|
||||
|
||||
return $this->template->render('config/form_display/group_header', [
|
||||
'group' => $this->group,
|
||||
'colspan' => $colspan,
|
||||
'header_text' => $headerText,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display group footer
|
||||
*/
|
||||
public function displayGroupFooter(): void
|
||||
{
|
||||
$this->group--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends JS validation code to $js_array
|
||||
*
|
||||
* @param string $fieldId ID of field to validate
|
||||
* @param string|array $validators validators callback
|
||||
* @param array $jsArray will be updated with javascript code
|
||||
*/
|
||||
public function addJsValidate($fieldId, $validators, array &$jsArray): void
|
||||
{
|
||||
foreach ((array) $validators as $validator) {
|
||||
$validator = (array) $validator;
|
||||
$vName = array_shift($validator);
|
||||
$vArgs = [];
|
||||
foreach ($validator as $arg) {
|
||||
$vArgs[] = Sanitize::escapeJsString($arg);
|
||||
}
|
||||
|
||||
$vArgs = $vArgs ? ", ['" . implode("', '", $vArgs) . "']" : '';
|
||||
$jsArray[] = "registerFieldValidator('" . $fieldId . "', '" . $vName . "', true" . $vArgs . ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays error list
|
||||
*
|
||||
* @param string $name Name of item with errors
|
||||
* @param array $errorList List of errors to show
|
||||
*
|
||||
* @return string HTML for errors
|
||||
*/
|
||||
public function displayErrors($name, array $errorList): string
|
||||
{
|
||||
return $this->template->render('config/form_display/errors', [
|
||||
'name' => $name,
|
||||
'error_list' => $errorList,
|
||||
]);
|
||||
}
|
||||
|
||||
public function display(array $data): string
|
||||
{
|
||||
return $this->template->render('config/form_display/display', $data);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Base class for preferences.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\FormDisplay;
|
||||
|
||||
use function is_int;
|
||||
|
||||
/**
|
||||
* Base form for user preferences
|
||||
*/
|
||||
abstract class BaseForm extends FormDisplay
|
||||
{
|
||||
/**
|
||||
* @param ConfigFile $cf Config file instance
|
||||
* @param int|null $serverId 0 if new server, validation; >= 1 if editing a server
|
||||
*/
|
||||
final public function __construct(ConfigFile $cf, $serverId = null)
|
||||
{
|
||||
parent::__construct($cf);
|
||||
foreach (static::getForms() as $formName => $form) {
|
||||
$this->registerForm($formName, $form, $serverId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of available forms, each form is described as an array of fields to display.
|
||||
* Fields MUST have their counterparts in the $cfg array.
|
||||
*
|
||||
* To define form field, use the notation below:
|
||||
* $forms['Form group']['Form name'] = array('Option/path');
|
||||
*
|
||||
* You can assign default values set by special button ("set value: ..."), eg.:
|
||||
* 'Servers/1/pmadb' => 'phpmyadmin'
|
||||
*
|
||||
* To group options, use:
|
||||
* ':group:' . __('group name') // just define a group
|
||||
* or
|
||||
* 'option' => ':group' // group starting from this option
|
||||
* End group blocks with:
|
||||
* ':group:end'
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @todo This should be abstract, but that does not work in PHP 5
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of fields used in the form.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getFields()
|
||||
{
|
||||
$names = [];
|
||||
foreach (static::getForms() as $form) {
|
||||
foreach ($form as $k => $v) {
|
||||
$names[] = is_int($k) ? $v : $k;
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of the form
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @todo This should be abstract, but that does not work in PHP 5
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
class ExportForm extends \PhpMyAdmin\Config\Forms\User\ExportForm
|
||||
{
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
class NaviForm extends \PhpMyAdmin\Config\Forms\User\NaviForm
|
||||
{
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class ConfigForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Config' => [
|
||||
'DefaultLang',
|
||||
'ServerDefault',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
class ExportForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
// phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified,Squiz.Arrays.ArrayDeclaration.NoKeySpecified
|
||||
return [
|
||||
'Export_defaults' => [
|
||||
'Export/method',
|
||||
':group:' . __('Quick'),
|
||||
'Export/quick_export_onserver',
|
||||
'Export/quick_export_onserver_overwrite',
|
||||
':group:end',
|
||||
':group:' . __('Custom'),
|
||||
'Export/format',
|
||||
'Export/compression',
|
||||
'Export/charset',
|
||||
'Export/lock_tables',
|
||||
'Export/as_separate_files',
|
||||
'Export/asfile' => ':group',
|
||||
'Export/onserver',
|
||||
'Export/onserver_overwrite',
|
||||
':group:end',
|
||||
'Export/file_template_table',
|
||||
'Export/file_template_database',
|
||||
'Export/file_template_server',
|
||||
],
|
||||
'Sql' => [
|
||||
'Export/sql_include_comments' => ':group',
|
||||
'Export/sql_dates',
|
||||
'Export/sql_relation',
|
||||
'Export/sql_mime',
|
||||
':group:end',
|
||||
'Export/sql_use_transaction',
|
||||
'Export/sql_disable_fk',
|
||||
'Export/sql_views_as_tables',
|
||||
'Export/sql_metadata',
|
||||
'Export/sql_compatibility',
|
||||
'Export/sql_structure_or_data',
|
||||
':group:' . __('Structure'),
|
||||
'Export/sql_drop_database',
|
||||
'Export/sql_create_database',
|
||||
'Export/sql_drop_table',
|
||||
'Export/sql_create_table' => ':group',
|
||||
'Export/sql_if_not_exists',
|
||||
'Export/sql_auto_increment',
|
||||
':group:end',
|
||||
'Export/sql_create_view' => ':group',
|
||||
'Export/sql_view_current_user',
|
||||
'Export/sql_or_replace_view',
|
||||
':group:end',
|
||||
'Export/sql_procedure_function',
|
||||
'Export/sql_create_trigger',
|
||||
'Export/sql_backquotes',
|
||||
':group:end',
|
||||
':group:' . __('Data'),
|
||||
'Export/sql_delayed',
|
||||
'Export/sql_ignore',
|
||||
'Export/sql_type',
|
||||
'Export/sql_insert_syntax',
|
||||
'Export/sql_max_query_size',
|
||||
'Export/sql_hex_for_binary',
|
||||
'Export/sql_utc_time',
|
||||
],
|
||||
'CodeGen' => ['Export/codegen_format'],
|
||||
'Csv' => [
|
||||
':group:' . __('CSV'),
|
||||
'Export/csv_separator',
|
||||
'Export/csv_enclosed',
|
||||
'Export/csv_escaped',
|
||||
'Export/csv_terminated',
|
||||
'Export/csv_null',
|
||||
'Export/csv_removeCRLF',
|
||||
'Export/csv_columns',
|
||||
':group:end',
|
||||
':group:' . __('CSV for MS Excel'),
|
||||
'Export/excel_null',
|
||||
'Export/excel_removeCRLF',
|
||||
'Export/excel_columns',
|
||||
'Export/excel_edition',
|
||||
],
|
||||
'Latex' => [
|
||||
'Export/latex_caption',
|
||||
'Export/latex_structure_or_data',
|
||||
':group:' . __('Structure'),
|
||||
'Export/latex_structure_caption',
|
||||
'Export/latex_structure_continued_caption',
|
||||
'Export/latex_structure_label',
|
||||
'Export/latex_relation',
|
||||
'Export/latex_comments',
|
||||
'Export/latex_mime',
|
||||
':group:end',
|
||||
':group:' . __('Data'),
|
||||
'Export/latex_columns',
|
||||
'Export/latex_data_caption',
|
||||
'Export/latex_data_continued_caption',
|
||||
'Export/latex_data_label',
|
||||
'Export/latex_null',
|
||||
],
|
||||
'Microsoft_Office' => [
|
||||
':group:' . __('Microsoft Word 2000'),
|
||||
'Export/htmlword_structure_or_data',
|
||||
'Export/htmlword_null',
|
||||
'Export/htmlword_columns',
|
||||
],
|
||||
'Open_Document' => [
|
||||
':group:' . __('OpenDocument Spreadsheet'),
|
||||
'Export/ods_columns',
|
||||
'Export/ods_null',
|
||||
':group:end',
|
||||
':group:' . __('OpenDocument Text'),
|
||||
'Export/odt_structure_or_data',
|
||||
':group:' . __('Structure'),
|
||||
'Export/odt_relation',
|
||||
'Export/odt_comments',
|
||||
'Export/odt_mime',
|
||||
':group:end',
|
||||
':group:' . __('Data'),
|
||||
'Export/odt_columns',
|
||||
'Export/odt_null',
|
||||
],
|
||||
'Texy' => [
|
||||
'Export/texytext_structure_or_data',
|
||||
':group:' . __('Data'),
|
||||
'Export/texytext_null',
|
||||
'Export/texytext_columns',
|
||||
],
|
||||
];
|
||||
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('Export');
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
class SqlForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Sql_queries' => [
|
||||
'ShowSQL',
|
||||
'Confirm',
|
||||
'QueryHistoryMax',
|
||||
'IgnoreMultiSubmitErrors',
|
||||
'MaxCharactersInDisplayedSQL',
|
||||
'RetainQueryBox',
|
||||
'CodemirrorEnable',
|
||||
'LintEnable',
|
||||
'EnableAutocompleteForTablesAndColumns',
|
||||
'DefaultForeignKeyChecks',
|
||||
],
|
||||
'Sql_box' => [
|
||||
'SQLQuery/Edit',
|
||||
'SQLQuery/Explain',
|
||||
'SQLQuery/ShowAsPHP',
|
||||
'SQLQuery/Refresh',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('SQL queries');
|
||||
}
|
||||
}
|
@ -0,0 +1,553 @@
|
||||
<?php
|
||||
/**
|
||||
* Server config checks management
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Setup\Index as SetupIndex;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
use function __;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function ini_get;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Performs various compatibility, security and consistency checks on current config
|
||||
*
|
||||
* Outputs results to message list, must be called between SetupIndex::messagesBegin()
|
||||
* and SetupIndex::messagesEnd()
|
||||
*/
|
||||
class ServerConfigChecks
|
||||
{
|
||||
/** @var ConfigFile configurations being checked */
|
||||
protected $cfg;
|
||||
|
||||
/**
|
||||
* @param ConfigFile $cfg Configuration
|
||||
*/
|
||||
public function __construct(ConfigFile $cfg)
|
||||
{
|
||||
$this->cfg = $cfg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform config checks
|
||||
*/
|
||||
public function performConfigChecks(): void
|
||||
{
|
||||
$blowfishSecret = $this->cfg->get('blowfish_secret');
|
||||
$blowfishSecretSet = false;
|
||||
$cookieAuthUsed = false;
|
||||
|
||||
[$cookieAuthUsed, $blowfishSecret, $blowfishSecretSet] = $this->performConfigChecksServers(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet
|
||||
);
|
||||
|
||||
$this->performConfigChecksCookieAuthUsed($cookieAuthUsed, $blowfishSecretSet, $blowfishSecret);
|
||||
|
||||
// $cfg['AllowArbitraryServer']
|
||||
// should be disabled
|
||||
if ($this->cfg->getValue('AllowArbitraryServer')) {
|
||||
$sAllowArbitraryServerWarn = sprintf(
|
||||
__(
|
||||
'This %soption%s should be disabled as it allows attackers to '
|
||||
. 'bruteforce login to any MySQL server. If you feel this is necessary, '
|
||||
. 'use %srestrict login to MySQL server%s or %strusted proxies list%s. '
|
||||
. 'However, IP-based protection with trusted proxies list may not be '
|
||||
. 'reliable if your IP belongs to an ISP where thousands of users, '
|
||||
. 'including you, are connected to.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
);
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'AllowArbitraryServer',
|
||||
Descriptions::get('AllowArbitraryServer'),
|
||||
Sanitize::sanitizeMessage($sAllowArbitraryServerWarn)
|
||||
);
|
||||
}
|
||||
|
||||
$this->performConfigChecksLoginCookie();
|
||||
|
||||
$sDirectoryNotice = __(
|
||||
'This value should be double checked to ensure that this directory is '
|
||||
. 'neither world accessible nor readable or writable by other users on '
|
||||
. 'your server.'
|
||||
);
|
||||
|
||||
// $cfg['SaveDir']
|
||||
// should not be world-accessible
|
||||
if ($this->cfg->getValue('SaveDir') != '') {
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'SaveDir',
|
||||
Descriptions::get('SaveDir'),
|
||||
Sanitize::sanitizeMessage($sDirectoryNotice)
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['TempDir']
|
||||
// should not be world-accessible
|
||||
if ($this->cfg->getValue('TempDir') != '') {
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'TempDir',
|
||||
Descriptions::get('TempDir'),
|
||||
Sanitize::sanitizeMessage($sDirectoryNotice)
|
||||
);
|
||||
}
|
||||
|
||||
$this->performConfigChecksZips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check config of servers
|
||||
*
|
||||
* @param bool $cookieAuthUsed Cookie auth is used
|
||||
* @param string $blowfishSecret Blowfish secret
|
||||
* @param bool $blowfishSecretSet Blowfish secret set
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function performConfigChecksServers(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet
|
||||
) {
|
||||
$serverCnt = $this->cfg->getServerCount();
|
||||
$isCookieAuthUsed = (int) $cookieAuthUsed;
|
||||
for ($i = 1; $i <= $serverCnt; $i++) {
|
||||
$cookieAuthServer = ($this->cfg->getValue('Servers/' . $i . '/auth_type') === 'cookie');
|
||||
$isCookieAuthUsed |= (int) $cookieAuthServer;
|
||||
$serverName = $this->performConfigChecksServersGetServerName(
|
||||
$this->cfg->getServerName($i),
|
||||
$i
|
||||
);
|
||||
$serverName = htmlspecialchars($serverName);
|
||||
|
||||
[$blowfishSecret, $blowfishSecretSet] = $this->performConfigChecksServersSetBlowfishSecret(
|
||||
$blowfishSecret,
|
||||
$cookieAuthServer,
|
||||
$blowfishSecretSet
|
||||
);
|
||||
|
||||
// $cfg['Servers'][$i]['ssl']
|
||||
// should be enabled if possible
|
||||
if (! $this->cfg->getValue('Servers/' . $i . '/ssl')) {
|
||||
$title = Descriptions::get('Servers/1/ssl') . ' (' . $serverName . ')';
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'Servers/' . $i . '/ssl',
|
||||
$title,
|
||||
__(
|
||||
'You should use SSL connections if your database server supports it.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$sSecurityInfoMsg = Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'If you feel this is necessary, use additional protection settings - '
|
||||
. '%1$shost authentication%2$s settings and %3$strusted proxies list%4$s. '
|
||||
. 'However, IP-based protection may not be reliable if your IP belongs '
|
||||
. 'to an ISP where thousands of users, including you, are connected to.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server_config]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
));
|
||||
|
||||
// $cfg['Servers'][$i]['auth_type']
|
||||
// warn about full user credentials if 'auth_type' is 'config'
|
||||
if (
|
||||
$this->cfg->getValue('Servers/' . $i . '/auth_type') === 'config'
|
||||
&& $this->cfg->getValue('Servers/' . $i . '/user') != ''
|
||||
&& $this->cfg->getValue('Servers/' . $i . '/password') != ''
|
||||
) {
|
||||
$title = Descriptions::get('Servers/1/auth_type')
|
||||
. ' (' . $serverName . ')';
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'Servers/' . $i . '/auth_type',
|
||||
$title,
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'You set the [kbd]config[/kbd] authentication type and included '
|
||||
. 'username and password for auto-login, which is not a desirable '
|
||||
. 'option for live hosts. Anyone who knows or guesses your phpMyAdmin '
|
||||
. 'URL can directly access your phpMyAdmin panel. Set %1$sauthentication '
|
||||
. 'type%2$s to [kbd]cookie[/kbd] or [kbd]http[/kbd].'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server]',
|
||||
'[/a]'
|
||||
))
|
||||
. ' ' . $sSecurityInfoMsg
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['Servers'][$i]['AllowRoot']
|
||||
// $cfg['Servers'][$i]['AllowNoPassword']
|
||||
// serious security flaw
|
||||
if (
|
||||
! $this->cfg->getValue('Servers/' . $i . '/AllowRoot')
|
||||
|| ! $this->cfg->getValue('Servers/' . $i . '/AllowNoPassword')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = Descriptions::get('Servers/1/AllowNoPassword')
|
||||
. ' (' . $serverName . ')';
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'Servers/' . $i . '/AllowNoPassword',
|
||||
$title,
|
||||
__('You allow for connecting to the server without a password.')
|
||||
. ' ' . $sSecurityInfoMsg
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
(bool) $isCookieAuthUsed,
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set blowfish secret
|
||||
*
|
||||
* @param string|null $blowfishSecret Blowfish secret
|
||||
* @param bool $cookieAuthServer Cookie auth is used
|
||||
* @param bool $blowfishSecretSet Blowfish secret set
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function performConfigChecksServersSetBlowfishSecret(
|
||||
$blowfishSecret,
|
||||
$cookieAuthServer,
|
||||
$blowfishSecretSet
|
||||
): array {
|
||||
if ($cookieAuthServer && $blowfishSecret === null) {
|
||||
$blowfishSecretSet = true;
|
||||
$this->cfg->set('blowfish_secret', Util::generateRandom(32));
|
||||
}
|
||||
|
||||
return [
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define server name
|
||||
*
|
||||
* @param string $serverName Server name
|
||||
* @param int $serverId Server id
|
||||
*
|
||||
* @return string Server name
|
||||
*/
|
||||
protected function performConfigChecksServersGetServerName(
|
||||
$serverName,
|
||||
$serverId
|
||||
) {
|
||||
if ($serverName === 'localhost') {
|
||||
return $serverName . ' [' . $serverId . ']';
|
||||
}
|
||||
|
||||
return $serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform config checks for zip part.
|
||||
*/
|
||||
protected function performConfigChecksZips(): void
|
||||
{
|
||||
$this->performConfigChecksServerGZipdump();
|
||||
$this->performConfigChecksServerBZipdump();
|
||||
$this->performConfigChecksServersZipdump();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform config checks for zip part.
|
||||
*/
|
||||
protected function performConfigChecksServersZipdump(): void
|
||||
{
|
||||
// $cfg['ZipDump']
|
||||
// requires zip_open in import
|
||||
if ($this->cfg->getValue('ZipDump') && ! $this->functionExists('zip_open')) {
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'ZipDump_import',
|
||||
Descriptions::get('ZipDump'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%sZip decompression%s requires functions (%s) which are unavailable on this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
'zip_open'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['ZipDump']
|
||||
// requires gzcompress in export
|
||||
if (! $this->cfg->getValue('ZipDump') || $this->functionExists('gzcompress')) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'ZipDump_export',
|
||||
Descriptions::get('ZipDump'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%sZip compression%s requires functions (%s) which are unavailable on this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
'gzcompress'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check config of servers
|
||||
*
|
||||
* @param bool $cookieAuthUsed Cookie auth is used
|
||||
* @param bool $blowfishSecretSet Blowfish secret set
|
||||
* @param string $blowfishSecret Blowfish secret
|
||||
*/
|
||||
protected function performConfigChecksCookieAuthUsed(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecretSet,
|
||||
$blowfishSecret
|
||||
): void {
|
||||
// $cfg['blowfish_secret']
|
||||
// it's required for 'cookie' authentication
|
||||
if (! $cookieAuthUsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($blowfishSecretSet) {
|
||||
// 'cookie' auth used, blowfish_secret was generated
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'blowfish_secret_created',
|
||||
Descriptions::get('blowfish_secret'),
|
||||
Sanitize::sanitizeMessage(__(
|
||||
'You didn\'t have blowfish secret set and have enabled '
|
||||
. '[kbd]cookie[/kbd] authentication, so a key was automatically '
|
||||
. 'generated for you. It is used to encrypt cookies; you don\'t need to '
|
||||
. 'remember it.'
|
||||
))
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$blowfishWarnings = [];
|
||||
// check length
|
||||
if (strlen($blowfishSecret) < 32) {
|
||||
// too short key
|
||||
$blowfishWarnings[] = __('Key is too short, it should have at least 32 characters.');
|
||||
}
|
||||
|
||||
// check used characters
|
||||
$hasDigits = (bool) preg_match('/\d/', $blowfishSecret);
|
||||
$hasChars = (bool) preg_match('/\S/', $blowfishSecret);
|
||||
$hasNonword = (bool) preg_match('/\W/', $blowfishSecret);
|
||||
if (! $hasDigits || ! $hasChars || ! $hasNonword) {
|
||||
$blowfishWarnings[] = Sanitize::sanitizeMessage(
|
||||
__(
|
||||
'Key should contain letters, numbers [em]and[/em] special characters.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($blowfishWarnings)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'blowfish_warnings' . count($blowfishWarnings),
|
||||
Descriptions::get('blowfish_secret'),
|
||||
implode('<br>', $blowfishWarnings)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check configuration for login cookie
|
||||
*/
|
||||
protected function performConfigChecksLoginCookie(): void
|
||||
{
|
||||
// $cfg['LoginCookieValidity']
|
||||
// value greater than session.gc_maxlifetime will cause
|
||||
// random session invalidation after that time
|
||||
$loginCookieValidity = $this->cfg->getValue('LoginCookieValidity');
|
||||
if ($loginCookieValidity > ini_get('session.gc_maxlifetime')) {
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'LoginCookieValidity',
|
||||
Descriptions::get('LoginCookieValidity'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%1$sLogin cookie validity%2$s greater than %3$ssession.gc_maxlifetime%4$s may '
|
||||
. 'cause random session invalidation (currently session.gc_maxlifetime '
|
||||
. 'is %5$d).'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Core::getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']',
|
||||
'[/a]',
|
||||
ini_get('session.gc_maxlifetime')
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['LoginCookieValidity']
|
||||
// should be at most 1800 (30 min)
|
||||
if ($loginCookieValidity > 1800) {
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'LoginCookieValidity',
|
||||
Descriptions::get('LoginCookieValidity'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) '
|
||||
. 'at most. Values larger than 1800 may pose a security risk such as '
|
||||
. 'impersonation.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['LoginCookieValidity']
|
||||
// $cfg['LoginCookieStore']
|
||||
// LoginCookieValidity must be less or equal to LoginCookieStore
|
||||
if (
|
||||
($this->cfg->getValue('LoginCookieStore') == 0)
|
||||
|| ($loginCookieValidity <= $this->cfg->getValue('LoginCookieStore'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'LoginCookieValidity',
|
||||
Descriptions::get('LoginCookieValidity'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'If using [kbd]cookie[/kbd] authentication and %sLogin cookie store%s '
|
||||
. 'is not 0, %sLogin cookie validity%s must be set to a value less or '
|
||||
. 'equal to it.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check GZipDump configuration
|
||||
*/
|
||||
protected function performConfigChecksServerBZipdump(): void
|
||||
{
|
||||
// $cfg['BZipDump']
|
||||
// requires bzip2 functions
|
||||
if (
|
||||
! $this->cfg->getValue('BZipDump')
|
||||
|| ($this->functionExists('bzopen') && $this->functionExists('bzcompress'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$functions = $this->functionExists('bzopen')
|
||||
? '' :
|
||||
'bzopen';
|
||||
$functions .= $this->functionExists('bzcompress')
|
||||
? ''
|
||||
: ($functions ? ', ' : '') . 'bzcompress';
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'BZipDump',
|
||||
Descriptions::get('BZipDump'),
|
||||
Sanitize::sanitizeMessage(
|
||||
sprintf(
|
||||
__(
|
||||
'%1$sBzip2 compression and decompression%2$s requires functions (%3$s) which '
|
||||
. 'are unavailable on this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
$functions
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check GZipDump configuration
|
||||
*/
|
||||
protected function performConfigChecksServerGZipdump(): void
|
||||
{
|
||||
// $cfg['GZipDump']
|
||||
// requires zlib functions
|
||||
if (
|
||||
! $this->cfg->getValue('GZipDump')
|
||||
|| ($this->functionExists('gzopen') && $this->functionExists('gzencode'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'GZipDump',
|
||||
Descriptions::get('GZipDump'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%1$sGZip compression and decompression%2$s requires functions (%3$s) which '
|
||||
. 'are unavailable on this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
'gzencode'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around function_exists to allow mock in test
|
||||
*
|
||||
* @param string $name Function name
|
||||
*/
|
||||
protected function functionExists($name): bool
|
||||
{
|
||||
return function_exists($name);
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
use function in_array;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Console
|
||||
{
|
||||
/** @var bool */
|
||||
public $StartHistory;
|
||||
|
||||
/** @var bool */
|
||||
public $AlwaysExpand;
|
||||
|
||||
/** @var bool */
|
||||
public $CurrentQuery;
|
||||
|
||||
/** @var bool */
|
||||
public $EnterExecutes;
|
||||
|
||||
/** @var bool */
|
||||
public $DarkTheme;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'info'|'show'|'collapse'
|
||||
*/
|
||||
public $Mode;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var positive-int
|
||||
*/
|
||||
public $Height;
|
||||
|
||||
/** @var bool */
|
||||
public $GroupQueries;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'exec'|'time'|'count'
|
||||
*/
|
||||
public $OrderBy;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'asc'|'desc'
|
||||
*/
|
||||
public $Order;
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
public function __construct(array $console = [])
|
||||
{
|
||||
$this->StartHistory = $this->setStartHistory($console);
|
||||
$this->AlwaysExpand = $this->setAlwaysExpand($console);
|
||||
$this->CurrentQuery = $this->setCurrentQuery($console);
|
||||
$this->EnterExecutes = $this->setEnterExecutes($console);
|
||||
$this->DarkTheme = $this->setDarkTheme($console);
|
||||
$this->Mode = $this->setMode($console);
|
||||
$this->Height = $this->setHeight($console);
|
||||
$this->GroupQueries = $this->setGroupQueries($console);
|
||||
$this->OrderBy = $this->setOrderBy($console);
|
||||
$this->Order = $this->setOrder($console);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setStartHistory(array $console): bool
|
||||
{
|
||||
if (isset($console['StartHistory'])) {
|
||||
return (bool) $console['StartHistory'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setAlwaysExpand(array $console): bool
|
||||
{
|
||||
if (isset($console['AlwaysExpand'])) {
|
||||
return (bool) $console['AlwaysExpand'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setCurrentQuery(array $console): bool
|
||||
{
|
||||
if (isset($console['CurrentQuery'])) {
|
||||
return (bool) $console['CurrentQuery'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setEnterExecutes(array $console): bool
|
||||
{
|
||||
if (isset($console['EnterExecutes'])) {
|
||||
return (bool) $console['EnterExecutes'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setDarkTheme(array $console): bool
|
||||
{
|
||||
if (isset($console['DarkTheme'])) {
|
||||
return (bool) $console['DarkTheme'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return 'info'|'show'|'collapse'
|
||||
*/
|
||||
private function setMode(array $console): string
|
||||
{
|
||||
if (isset($console['Mode']) && in_array($console['Mode'], ['show', 'collapse'], true)) {
|
||||
return $console['Mode'];
|
||||
}
|
||||
|
||||
return 'info';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return positive-int
|
||||
*/
|
||||
private function setHeight(array $console): int
|
||||
{
|
||||
if (isset($console['Height'])) {
|
||||
$height = (int) $console['Height'];
|
||||
if ($height >= 1) {
|
||||
return $height;
|
||||
}
|
||||
}
|
||||
|
||||
return 92;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setGroupQueries(array $console): bool
|
||||
{
|
||||
if (isset($console['GroupQueries'])) {
|
||||
return (bool) $console['GroupQueries'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return 'exec'|'time'|'count'
|
||||
*/
|
||||
private function setOrderBy(array $console): string
|
||||
{
|
||||
if (isset($console['OrderBy']) && in_array($console['OrderBy'], ['time', 'count'], true)) {
|
||||
return $console['OrderBy'];
|
||||
}
|
||||
|
||||
return 'exec';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return 'asc'|'desc'
|
||||
*/
|
||||
private function setOrder(array $console): string
|
||||
{
|
||||
if (isset($console['Order']) && $console['Order'] === 'desc') {
|
||||
return 'desc';
|
||||
}
|
||||
|
||||
return 'asc';
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Debug
|
||||
{
|
||||
/**
|
||||
* Output executed queries and their execution times.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $sql;
|
||||
|
||||
/**
|
||||
* Log executed queries and their execution times to syslog.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $sqllog;
|
||||
|
||||
/**
|
||||
* Enable to let server present itself as demo server.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $demo;
|
||||
|
||||
/**
|
||||
* Enable Simple two-factor authentication.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $simple2fa;
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
public function __construct(array $debug = [])
|
||||
{
|
||||
$this->sql = $this->setSql($debug);
|
||||
$this->sqllog = $this->setSqlLog($debug);
|
||||
$this->demo = $this->setDemo($debug);
|
||||
$this->simple2fa = $this->setSimple2fa($debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setSql(array $debug): bool
|
||||
{
|
||||
return isset($debug['sql']) && $debug['sql'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setSqlLog(array $debug): bool
|
||||
{
|
||||
return isset($debug['sqllog']) && $debug['sqllog'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setDemo(array $debug): bool
|
||||
{
|
||||
return isset($debug['demo']) && $debug['demo'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setSimple2fa(array $debug): bool
|
||||
{
|
||||
return isset($debug['simple2fa']) && $debug['simple2fa'];
|
||||
}
|
||||
}
|
@ -0,0 +1,489 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Import
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'csv'|'docsql'|'ldi'|'sql'
|
||||
*/
|
||||
public $format;
|
||||
|
||||
/**
|
||||
* Default charset for import.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $charset;
|
||||
|
||||
/** @var bool */
|
||||
public $allow_interrupt;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var 0|positive-int
|
||||
*/
|
||||
public $skip_queries;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'NONE'|'ANSI'|'DB2'|'MAXDB'|'MYSQL323'|'MYSQL40'|'MSSQL'|'ORACLE'|'TRADITIONAL'
|
||||
*/
|
||||
public $sql_compatibility;
|
||||
|
||||
/** @var bool */
|
||||
public $sql_no_auto_value_on_zero;
|
||||
|
||||
/** @var bool */
|
||||
public $sql_read_as_multibytes;
|
||||
|
||||
/** @var bool */
|
||||
public $csv_replace;
|
||||
|
||||
/** @var bool */
|
||||
public $csv_ignore;
|
||||
|
||||
/** @var string */
|
||||
public $csv_terminated;
|
||||
|
||||
/** @var string */
|
||||
public $csv_enclosed;
|
||||
|
||||
/** @var string */
|
||||
public $csv_escaped;
|
||||
|
||||
/** @var string */
|
||||
public $csv_new_line;
|
||||
|
||||
/** @var string */
|
||||
public $csv_columns;
|
||||
|
||||
/** @var bool */
|
||||
public $csv_col_names;
|
||||
|
||||
/** @var bool */
|
||||
public $ldi_replace;
|
||||
|
||||
/** @var bool */
|
||||
public $ldi_ignore;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_terminated;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_enclosed;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_escaped;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_new_line;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_columns;
|
||||
|
||||
/**
|
||||
* 'auto' for auto-detection, true or false for forcing
|
||||
*
|
||||
* @var string|bool
|
||||
* @psalm-var 'auto'|bool
|
||||
*/
|
||||
public $ldi_local_option;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_col_names;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_empty_rows;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_recognize_percentages;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_recognize_currency;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
public function __construct(array $import = [])
|
||||
{
|
||||
$this->format = $this->setFormat($import);
|
||||
$this->charset = $this->setCharset($import);
|
||||
$this->allow_interrupt = $this->setAllowInterrupt($import);
|
||||
$this->skip_queries = $this->setSkipQueries($import);
|
||||
$this->sql_compatibility = $this->setSqlCompatibility($import);
|
||||
$this->sql_no_auto_value_on_zero = $this->setSqlNoAutoValueOnZero($import);
|
||||
$this->sql_read_as_multibytes = $this->setSqlReadAsMultibytes($import);
|
||||
$this->csv_replace = $this->setCsvReplace($import);
|
||||
$this->csv_ignore = $this->setCsvIgnore($import);
|
||||
$this->csv_terminated = $this->setCsvTerminated($import);
|
||||
$this->csv_enclosed = $this->setCsvEnclosed($import);
|
||||
$this->csv_escaped = $this->setCsvEscaped($import);
|
||||
$this->csv_new_line = $this->setCsvNewLine($import);
|
||||
$this->csv_columns = $this->setCsvColumns($import);
|
||||
$this->csv_col_names = $this->setCsvColNames($import);
|
||||
$this->ldi_replace = $this->setLdiReplace($import);
|
||||
$this->ldi_ignore = $this->setLdiIgnore($import);
|
||||
$this->ldi_terminated = $this->setLdiTerminated($import);
|
||||
$this->ldi_enclosed = $this->setLdiEnclosed($import);
|
||||
$this->ldi_escaped = $this->setLdiEscaped($import);
|
||||
$this->ldi_new_line = $this->setLdiNewLine($import);
|
||||
$this->ldi_columns = $this->setLdiColumns($import);
|
||||
$this->ldi_local_option = $this->setLdiLocalOption($import);
|
||||
$this->ods_col_names = $this->setOdsColNames($import);
|
||||
$this->ods_empty_rows = $this->setOdsEmptyRows($import);
|
||||
$this->ods_recognize_percentages = $this->setOdsRecognizePercentages($import);
|
||||
$this->ods_recognize_currency = $this->setOdsRecognizeCurrency($import);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @psalm-return 'csv'|'docsql'|'ldi'|'sql'
|
||||
*/
|
||||
private function setFormat(array $import): string
|
||||
{
|
||||
if (! isset($import['format']) || ! in_array($import['format'], ['csv', 'docsql', 'ldi'], true)) {
|
||||
return 'sql';
|
||||
}
|
||||
|
||||
return $import['format'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCharset(array $import): string
|
||||
{
|
||||
if (! isset($import['charset'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $import['charset'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setAllowInterrupt(array $import): bool
|
||||
{
|
||||
if (! isset($import['allow_interrupt'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['allow_interrupt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @psalm-return 0|positive-int
|
||||
*/
|
||||
private function setSkipQueries(array $import): int
|
||||
{
|
||||
if (! isset($import['skip_queries'])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$skipQueries = (int) $import['skip_queries'];
|
||||
|
||||
return $skipQueries >= 1 ? $skipQueries : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @psalm-return 'NONE'|'ANSI'|'DB2'|'MAXDB'|'MYSQL323'|'MYSQL40'|'MSSQL'|'ORACLE'|'TRADITIONAL'
|
||||
*/
|
||||
private function setSqlCompatibility(array $import): string
|
||||
{
|
||||
if (
|
||||
! isset($import['sql_compatibility']) || ! in_array(
|
||||
$import['sql_compatibility'],
|
||||
['ANSI', 'DB2', 'MAXDB', 'MYSQL323', 'MYSQL40', 'MSSQL', 'ORACLE', 'TRADITIONAL'],
|
||||
true
|
||||
)
|
||||
) {
|
||||
return 'NONE';
|
||||
}
|
||||
|
||||
return $import['sql_compatibility'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setSqlNoAutoValueOnZero(array $import): bool
|
||||
{
|
||||
if (! isset($import['sql_no_auto_value_on_zero'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['sql_no_auto_value_on_zero'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setSqlReadAsMultibytes(array $import): bool
|
||||
{
|
||||
if (! isset($import['sql_read_as_multibytes'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['sql_read_as_multibytes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvReplace(array $import): bool
|
||||
{
|
||||
if (! isset($import['csv_replace'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['csv_replace'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvIgnore(array $import): bool
|
||||
{
|
||||
if (! isset($import['csv_ignore'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['csv_ignore'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvTerminated(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_terminated'])) {
|
||||
return ',';
|
||||
}
|
||||
|
||||
return (string) $import['csv_terminated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvEnclosed(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_enclosed'])) {
|
||||
return '"';
|
||||
}
|
||||
|
||||
return (string) $import['csv_enclosed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvEscaped(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_escaped'])) {
|
||||
return '"';
|
||||
}
|
||||
|
||||
return (string) $import['csv_escaped'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvNewLine(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_new_line'])) {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
return (string) $import['csv_new_line'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvColumns(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_columns'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $import['csv_columns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvColNames(array $import): bool
|
||||
{
|
||||
if (! isset($import['csv_col_names'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['csv_col_names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiReplace(array $import): bool
|
||||
{
|
||||
if (! isset($import['ldi_replace'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['ldi_replace'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiIgnore(array $import): bool
|
||||
{
|
||||
if (! isset($import['ldi_ignore'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['ldi_ignore'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiTerminated(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_terminated'])) {
|
||||
return ';';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_terminated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiEnclosed(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_enclosed'])) {
|
||||
return '"';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_enclosed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiEscaped(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_escaped'])) {
|
||||
return '\\';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_escaped'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiNewLine(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_new_line'])) {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_new_line'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiColumns(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_columns'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_columns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @return bool|string
|
||||
* @psalm-return 'auto'|bool
|
||||
*/
|
||||
private function setLdiLocalOption(array $import)
|
||||
{
|
||||
if (! isset($import['ldi_local_option']) || $import['ldi_local_option'] === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
return (bool) $import['ldi_local_option'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsColNames(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_col_names'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_col_names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsEmptyRows(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_empty_rows'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_empty_rows'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsRecognizePercentages(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_recognize_percentages'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_recognize_percentages'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsRecognizeCurrency(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_recognize_currency'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_recognize_currency'];
|
||||
}
|
||||
}
|
@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
use function is_array;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Transformations
|
||||
{
|
||||
/**
|
||||
* Displays a part of a string.
|
||||
* - The first option is the number of characters to skip from the beginning of the string (Default 0).
|
||||
* - The second option is the number of characters to return (Default: until end of string).
|
||||
* - The third option is the string to append and/or prepend when truncation occurs (Default: "…").
|
||||
*
|
||||
* @var array<int, int|string>
|
||||
* @psalm-var array{0: int, 1: 'all'|int, 2: string}
|
||||
*/
|
||||
public $Substring;
|
||||
|
||||
/**
|
||||
* Converts Boolean values to text (default 'T' and 'F').
|
||||
* - First option is for TRUE, second for FALSE. Nonzero=true.
|
||||
*
|
||||
* @var string[]
|
||||
* @psalm-var array{0: string, 1: string}
|
||||
*/
|
||||
public $Bool2Text;
|
||||
|
||||
/**
|
||||
* LINUX ONLY: Launches an external application and feeds it the column data via standard input.
|
||||
* Returns the standard output of the application. The default is Tidy, to pretty-print HTML code.
|
||||
* For security reasons, you have to manually edit the file
|
||||
* libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php and list the tools
|
||||
* you want to make available.
|
||||
* - The first option is then the number of the program you want to use.
|
||||
* - The second option should be blank for historical reasons.
|
||||
* - The third option, if set to 1, will convert the output using htmlspecialchars() (Default 1).
|
||||
* - The fourth option, if set to 1, will prevent wrapping and ensure that the output appears
|
||||
* all on one line (Default 1).
|
||||
*
|
||||
* @var array<int, int|string>
|
||||
* @psalm-var array{0: int, 1: string, 2: int, 3: int}
|
||||
*/
|
||||
public $External;
|
||||
|
||||
/**
|
||||
* Prepends and/or Appends text to a string.
|
||||
* - First option is text to be prepended. second is appended (enclosed in single quotes, default empty string).
|
||||
*
|
||||
* @var string[]
|
||||
* @psalm-var array{0: string, 1: string}
|
||||
*/
|
||||
public $PreApPend;
|
||||
|
||||
/**
|
||||
* Displays hexadecimal representation of data.
|
||||
* Optional first parameter specifies how often space will be added (defaults to 2 nibbles).
|
||||
*
|
||||
* @var string[]
|
||||
* @psalm-var array{0: 0|positive-int}
|
||||
*/
|
||||
public $Hex;
|
||||
|
||||
/**
|
||||
* Displays a TIME, TIMESTAMP, DATETIME or numeric unix timestamp column as formatted date.
|
||||
* - The first option is the offset (in hours) which will be added to the timestamp (Default: 0).
|
||||
* - Use second option to specify a different date/time format string.
|
||||
* - Third option determines whether you want to see local date or UTC one (use "local" or "utc" strings) for that.
|
||||
* According to that, date format has different value - for "local" see the documentation
|
||||
* for PHP's strftime() function and for "utc" it is done using gmdate() function.
|
||||
*
|
||||
* @var array<int, int|string>
|
||||
* @psalm-var array{0: 0|positive-int, 1: string, 2: 'local'|'utc'}
|
||||
*/
|
||||
public $DateFormat;
|
||||
|
||||
/**
|
||||
* Displays a clickable thumbnail.
|
||||
* The options are the maximum width and height in pixels.
|
||||
* The original aspect ratio is preserved.
|
||||
*
|
||||
* @var array<(int|string), (int|string|array<string, string>|null)>
|
||||
* @psalm-var array{
|
||||
* 0: 0|positive-int,
|
||||
* 1: 0|positive-int,
|
||||
* wrapper_link: string|null,
|
||||
* wrapper_params: array<array-key, string>
|
||||
* }
|
||||
*/
|
||||
public $Inline;
|
||||
|
||||
/**
|
||||
* Displays an image and a link; the column contains the filename.
|
||||
* - The first option is a URL prefix like "https://www.example.com/".
|
||||
* - The second and third options are the width and the height in pixels.
|
||||
*
|
||||
* @var array<int, int|string|null>
|
||||
* @psalm-var array{0: string|null, 1: 0|positive-int, 2: 0|positive-int}
|
||||
*/
|
||||
public $TextImageLink;
|
||||
|
||||
/**
|
||||
* Displays a link; the column contains the filename.
|
||||
* - The first option is a URL prefix like "https://www.example.com/".
|
||||
* - The second option is a title for the link.
|
||||
*
|
||||
* @var array<int, string|null>
|
||||
* @psalm-var array{0: string|null, 1: string|null, 2: bool|null}
|
||||
*/
|
||||
public $TextLink;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*/
|
||||
public function __construct(array $transformations = [])
|
||||
{
|
||||
$this->Substring = $this->setSubstring($transformations);
|
||||
$this->Bool2Text = $this->setBool2Text($transformations);
|
||||
$this->External = $this->setExternal($transformations);
|
||||
$this->PreApPend = $this->setPreApPend($transformations);
|
||||
$this->Hex = $this->setHex($transformations);
|
||||
$this->DateFormat = $this->setDateFormat($transformations);
|
||||
$this->Inline = $this->setInline($transformations);
|
||||
$this->TextImageLink = $this->setTextImageLink($transformations);
|
||||
$this->TextLink = $this->setTextLink($transformations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string>
|
||||
* @psalm-return array{0: int, 1: 'all'|int, 2: string}
|
||||
*/
|
||||
private function setSubstring(array $transformations): array
|
||||
{
|
||||
$substring = [0, 'all', '…'];
|
||||
if (isset($transformations['Substring']) && is_array($transformations['Substring'])) {
|
||||
if (isset($transformations['Substring'][0])) {
|
||||
$substring[0] = (int) $transformations['Substring'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['Substring'][1]) && $transformations['Substring'][1] !== 'all') {
|
||||
$substring[1] = (int) $transformations['Substring'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['Substring'][2])) {
|
||||
$substring[2] = (string) $transformations['Substring'][2];
|
||||
}
|
||||
}
|
||||
|
||||
return $substring;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return array{0: string, 1: string}
|
||||
*/
|
||||
private function setBool2Text(array $transformations): array
|
||||
{
|
||||
$bool2Text = ['T', 'F'];
|
||||
if (isset($transformations['Bool2Text']) && is_array($transformations['Bool2Text'])) {
|
||||
if (isset($transformations['Bool2Text'][0])) {
|
||||
$bool2Text[0] = (string) $transformations['Bool2Text'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['Bool2Text'][1])) {
|
||||
$bool2Text[1] = (string) $transformations['Bool2Text'][1];
|
||||
}
|
||||
}
|
||||
|
||||
return $bool2Text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string>
|
||||
* @psalm-return array{0: int, 1: string, 2: int, 3: int}
|
||||
*/
|
||||
private function setExternal(array $transformations): array
|
||||
{
|
||||
$external = [0, '-f /dev/null -i -wrap -q', 1, 1];
|
||||
if (isset($transformations['External']) && is_array($transformations['External'])) {
|
||||
if (isset($transformations['External'][0])) {
|
||||
$external[0] = (int) $transformations['External'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['External'][1])) {
|
||||
$external[1] = (string) $transformations['External'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['External'][2])) {
|
||||
$external[2] = (int) $transformations['External'][2];
|
||||
}
|
||||
|
||||
if (isset($transformations['External'][3])) {
|
||||
$external[3] = (int) $transformations['External'][3];
|
||||
}
|
||||
}
|
||||
|
||||
return $external;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return array{0: string, 1: string}
|
||||
*/
|
||||
private function setPreApPend(array $transformations): array
|
||||
{
|
||||
$preApPend = ['', ''];
|
||||
if (isset($transformations['PreApPend']) && is_array($transformations['PreApPend'])) {
|
||||
if (isset($transformations['PreApPend'][0])) {
|
||||
$preApPend[0] = (string) $transformations['PreApPend'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['PreApPend'][1])) {
|
||||
$preApPend[1] = (string) $transformations['PreApPend'][1];
|
||||
}
|
||||
}
|
||||
|
||||
return $preApPend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return array{0: 0|positive-int}
|
||||
*/
|
||||
private function setHex(array $transformations): array
|
||||
{
|
||||
if (isset($transformations['Hex']) && is_array($transformations['Hex'])) {
|
||||
if (isset($transformations['Hex'][0])) {
|
||||
$length = (int) $transformations['Hex'][0];
|
||||
if ($length >= 0) {
|
||||
return [$length];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string>
|
||||
* @psalm-return array{0: 0|positive-int, 1: string, 2: 'local'|'utc'}
|
||||
*/
|
||||
private function setDateFormat(array $transformations): array
|
||||
{
|
||||
$dateFormat = [0, '', 'local'];
|
||||
if (isset($transformations['DateFormat']) && is_array($transformations['DateFormat'])) {
|
||||
if (isset($transformations['DateFormat'][0])) {
|
||||
$offset = (int) $transformations['DateFormat'][0];
|
||||
if ($offset >= 1) {
|
||||
$dateFormat[0] = $offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['DateFormat'][1])) {
|
||||
$dateFormat[1] = (string) $transformations['DateFormat'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['DateFormat'][2]) && $transformations['DateFormat'][2] === 'utc') {
|
||||
$dateFormat[2] = 'utc';
|
||||
}
|
||||
}
|
||||
|
||||
return $dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<(int|string), (int|string|array<string, string>|null)>
|
||||
* @psalm-return array{
|
||||
* 0: 0|positive-int,
|
||||
* 1: 0|positive-int,
|
||||
* wrapper_link: string|null,
|
||||
* wrapper_params: array<array-key, string>
|
||||
* }
|
||||
*/
|
||||
private function setInline(array $transformations): array
|
||||
{
|
||||
$inline = [100, 100, 'wrapper_link' => null, 'wrapper_params' => []];
|
||||
if (isset($transformations['Inline']) && is_array($transformations['Inline'])) {
|
||||
if (isset($transformations['Inline'][0])) {
|
||||
$width = (int) $transformations['Inline'][0];
|
||||
if ($width >= 0) {
|
||||
$inline[0] = $width;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['Inline'][1])) {
|
||||
$height = (int) $transformations['Inline'][1];
|
||||
if ($height >= 0) {
|
||||
$inline[1] = $height;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['Inline']['wrapper_link'])) {
|
||||
$inline['wrapper_link'] = (string) $transformations['Inline']['wrapper_link'];
|
||||
}
|
||||
|
||||
if (
|
||||
isset($transformations['Inline']['wrapper_params'])
|
||||
&& is_array($transformations['Inline']['wrapper_params'])
|
||||
) {
|
||||
/**
|
||||
* @var int|string $key
|
||||
* @var mixed $value
|
||||
*/
|
||||
foreach ($transformations['Inline']['wrapper_params'] as $key => $value) {
|
||||
$inline['wrapper_params'][$key] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string|null>
|
||||
* @psalm-return array{0: string|null, 1: 0|positive-int, 2: 0|positive-int}
|
||||
*/
|
||||
private function setTextImageLink(array $transformations): array
|
||||
{
|
||||
$textImageLink = [null, 100, 50];
|
||||
if (isset($transformations['TextImageLink']) && is_array($transformations['TextImageLink'])) {
|
||||
if (isset($transformations['TextImageLink'][0])) {
|
||||
$textImageLink[0] = (string) $transformations['TextImageLink'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['TextImageLink'][1])) {
|
||||
$width = (int) $transformations['TextImageLink'][1];
|
||||
if ($width >= 0) {
|
||||
$textImageLink[1] = $width;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['TextImageLink'][2])) {
|
||||
$height = (int) $transformations['TextImageLink'][2];
|
||||
if ($height >= 0) {
|
||||
$textImageLink[2] = $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $textImageLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
* @psalm-return array{0: string|null, 1: string|null, 2: bool|null}
|
||||
*/
|
||||
private function setTextLink(array $transformations): array
|
||||
{
|
||||
$textLink = [null, null, null];
|
||||
if (isset($transformations['TextLink']) && is_array($transformations['TextLink'])) {
|
||||
if (isset($transformations['TextLink'][0])) {
|
||||
$textLink[0] = (string) $transformations['TextLink'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['TextLink'][1])) {
|
||||
$textLink[1] = (string) $transformations['TextLink'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['TextLink'][2])) {
|
||||
$textLink[2] = (bool) $transformations['TextLink'][2];
|
||||
}
|
||||
}
|
||||
|
||||
return $textLink;
|
||||
}
|
||||
}
|
@ -0,0 +1,485 @@
|
||||
<?php
|
||||
/**
|
||||
* Links configuration for MySQL system tables
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
class SpecialSchemaLinks
|
||||
{
|
||||
/**
|
||||
* This array represent the details for generating links inside
|
||||
* special schemas like mysql, information_schema etc.
|
||||
* Major element represent a schema.
|
||||
* All the strings in this array represented in lower case
|
||||
*
|
||||
* Array structure ex:
|
||||
* array(
|
||||
* // Database name is the major element
|
||||
* 'mysql' => array(
|
||||
* // Table name
|
||||
* 'db' => array(
|
||||
* // Column name
|
||||
* 'user' => array(
|
||||
* // Main url param (can be an array where represent sql)
|
||||
* 'link_param' => 'username',
|
||||
* // Other url params
|
||||
* 'link_dependancy_params' => array(
|
||||
* 0 => array(
|
||||
* // URL parameter name
|
||||
* // (can be array where url param has static value)
|
||||
* 'param_info' => 'hostname',
|
||||
* // Column name related to url param
|
||||
* 'column_name' => 'host'
|
||||
* )
|
||||
* ),
|
||||
* // Page to link
|
||||
* 'default_page' => './' . Url::getFromRoute('/server/privileges')
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* @return array<string,array<string,array<string,array<string,array<int,array<string,string>>|string>>>>
|
||||
* @phpstan-return array<
|
||||
* string, array<
|
||||
* string, array<
|
||||
* string,
|
||||
* array{
|
||||
* 'link_param': string,
|
||||
* 'link_dependancy_params'?: array<
|
||||
* int,
|
||||
* array{'param_info': string, 'column_name': string}
|
||||
* >,
|
||||
* 'default_page': string
|
||||
* }>
|
||||
* >
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
public static function get(): array
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$defaultPage = './' . Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
|
||||
return [
|
||||
'mysql' => [
|
||||
'columns_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'Table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'db' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
'event' => [
|
||||
'name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'db',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/events', ['edit_item' => 1]),
|
||||
],
|
||||
|
||||
],
|
||||
'innodb_index_stats' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'database_name',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'index_name' => [
|
||||
'link_param' => 'index',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'database_name',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure'),
|
||||
],
|
||||
],
|
||||
'innodb_table_stats' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'database_name',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'proc' => [
|
||||
'name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
|
||||
],
|
||||
'specific_name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
|
||||
],
|
||||
],
|
||||
'proc_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'Host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
'routine_name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'Routine_type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
|
||||
],
|
||||
],
|
||||
'proxies_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'Host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
'tables_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'Host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'user' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
],
|
||||
'information_schema' => [
|
||||
'columns' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'key_column_usage' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'constraint_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
'referenced_table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'referenced_table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'referenced_column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'referenced_table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'referenced_table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'partitions' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'processlist' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
'referential_constraints' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'constraint_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'referenced_table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'constraint_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'routines' => [
|
||||
'routine_name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'routine_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'routine_type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines'),
|
||||
],
|
||||
],
|
||||
'schemata' => [
|
||||
'schema_name' => [
|
||||
'link_param' => 'db',
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'statistics' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'tables' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'table_constraints' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'views' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class BookmarkFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $bookmark;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $bookmark)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->bookmark = $bookmark;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class ExportTemplatesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $exportTemplates;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $exportTemplates)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->exportTemplates = $exportTemplates;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class FavoriteTablesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $favorite;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $favorite)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->favorite = $favorite;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class SqlHistoryFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $history;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $history)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->history = $history;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class TrackingFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $tracking;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $tracking)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->tracking = $tracking;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Relation;
|
||||
use PhpMyAdmin\Http\ServerRequest;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
use const SQL_DIR;
|
||||
|
||||
/**
|
||||
* Displays status of phpMyAdmin configuration storage
|
||||
*/
|
||||
class CheckRelationsController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, Relation $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequest $request): void
|
||||
{
|
||||
global $db, $cfg;
|
||||
|
||||
/** @var string|null $createPmaDb */
|
||||
$createPmaDb = $request->getParsedBodyParam('create_pmadb');
|
||||
/** @var string|null $fixAllPmaDb */
|
||||
$fixAllPmaDb = $request->getParsedBodyParam('fixall_pmadb');
|
||||
/** @var string|null $fixPmaDb */
|
||||
$fixPmaDb = $request->getParsedBodyParam('fix_pmadb');
|
||||
|
||||
$cfgStorageDbName = $this->relation->getConfigurationStorageDbName();
|
||||
|
||||
// If request for creating the pmadb
|
||||
if (isset($createPmaDb) && $this->relation->createPmaDatabase($cfgStorageDbName)) {
|
||||
$this->relation->fixPmaTables($cfgStorageDbName);
|
||||
}
|
||||
|
||||
// If request for creating all PMA tables.
|
||||
if (isset($fixAllPmaDb)) {
|
||||
$this->relation->fixPmaTables($db);
|
||||
}
|
||||
|
||||
// If request for creating missing PMA tables.
|
||||
if (isset($fixPmaDb)) {
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
$this->relation->fixPmaTables((string) $relationParameters->db);
|
||||
}
|
||||
|
||||
// Do not use any previous $relationParameters value as it could have changed after a successful fixPmaTables()
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
|
||||
$this->render('relation/check_relations', [
|
||||
'db' => $db,
|
||||
'zero_conf' => $cfg['ZeroConf'],
|
||||
'relation_parameters' => $relationParameters->toArray(),
|
||||
'sql_dir' => SQL_DIR,
|
||||
'config_storage_database_name' => $cfgStorageDbName,
|
||||
'are_config_storage_tables_defined' => $this->relation->arePmadbTablesDefined(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
final class CollationConnectionController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, Config $config)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
$this->config->setUserValue(
|
||||
null,
|
||||
'DefaultConnectionCollation',
|
||||
$_POST['collation_connection'],
|
||||
'utf8mb4_unicode_ci'
|
||||
);
|
||||
|
||||
$this->response->header('Location: index.php?route=/' . Url::getCommonRaw([], '&'));
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Events;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
use function strlen;
|
||||
|
||||
final class EventsController extends AbstractController
|
||||
{
|
||||
/** @var Events */
|
||||
private $events;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
string $db,
|
||||
Events $events,
|
||||
DatabaseInterface $dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->events = $events;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
global $db, $tables, $num_tables, $total_num_tables, $sub_part, $errors, $text_dir;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $cfg, $errorUrl;
|
||||
|
||||
$this->addScriptFiles(['database/events.js']);
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$errorUrl .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$this->events->handleEditor();
|
||||
$this->events->export();
|
||||
|
||||
$items = $this->dbi->getEvents($db);
|
||||
|
||||
$this->render('database/events/index', [
|
||||
'db' => $db,
|
||||
'items' => $items,
|
||||
'has_privilege' => Util::currentUserHasPrivilege('EVENT', $db),
|
||||
'scheduler_state' => $this->events->getEventSchedulerStatus(),
|
||||
'text_dir' => $text_dir,
|
||||
'is_ajax' => $this->response->isAjax() && empty($_REQUEST['ajax_page_request']),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Controller for database privileges
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Server\Privileges;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
/**
|
||||
* Controller for database privileges
|
||||
*/
|
||||
class PrivilegesController extends AbstractController
|
||||
{
|
||||
/** @var Privileges */
|
||||
private $privileges;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
string $db,
|
||||
Privileges $privileges,
|
||||
DatabaseInterface $dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->privileges = $privileges;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function __invoke(array $params): string
|
||||
{
|
||||
global $cfg, $text_dir;
|
||||
|
||||
$scriptName = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
|
||||
$privileges = [];
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$privileges = $this->privileges->getAllPrivileges($params['checkprivsdb']);
|
||||
}
|
||||
|
||||
return $this->template->render('database/privileges/index', [
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'db' => $params['checkprivsdb'],
|
||||
'database_url' => $scriptName,
|
||||
'text_dir' => $text_dir,
|
||||
'is_createuser' => $this->dbi->isCreateUser(),
|
||||
'is_grantuser' => $this->dbi->isGrantUser(),
|
||||
'privileges' => $privileges,
|
||||
]);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue