mirror of
https://github.com/YunoHost-Apps/kanboard_ynh.git
synced 2024-09-03 19:36:17 +02:00
Init files + sources
Kanboard 1.0.6
This commit is contained in:
parent
53c3f7a5b9
commit
5e51bcd6e9
315 changed files with 34626 additions and 0 deletions
35
manifest.json
Normal file
35
manifest.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
name Kanboard,
|
||||
id kanboard,
|
||||
description {
|
||||
en Kanboard is a simple visual task board web application,
|
||||
fr Kanboard est une application web de management de tâches simples
|
||||
},
|
||||
developer {
|
||||
name mbugeia,
|
||||
email ,
|
||||
url
|
||||
},
|
||||
multi_instance true,
|
||||
arguments {
|
||||
install [
|
||||
{
|
||||
name domain,
|
||||
ask {
|
||||
en Choose a domain for Kanboard,
|
||||
fr Choisissez un domaine pour Kanboard
|
||||
},
|
||||
example domain.org
|
||||
},
|
||||
{
|
||||
name path,
|
||||
ask {
|
||||
en Choose a path for Kanboard,
|
||||
fr Choisissez un chemin pour Kanboard
|
||||
},
|
||||
example kanboard,
|
||||
default kanboard
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
0
scripts/install
Normal file
0
scripts/install
Normal file
0
scripts/remove
Normal file
0
scripts/remove
Normal file
0
scripts/upgrade
Normal file
0
scripts/upgrade
Normal file
55
sources/.gitignore
vendored
Normal file
55
sources/.gitignore
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
*.pyc
|
||||
|
||||
# Packages #
|
||||
############
|
||||
# it's better to unpack these files and commit the raw source
|
||||
# git has its own built in compression methods
|
||||
*.7z
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
|
||||
# Logs and databases #
|
||||
######################
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
|
||||
# IDE generated files #
|
||||
######################
|
||||
.buildpath
|
||||
.project
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store
|
||||
ehthumbs.db
|
||||
Icon?
|
||||
Thumbs.db
|
||||
*.swp
|
||||
.*.swp
|
||||
*~
|
||||
*.lock
|
||||
*.out
|
||||
|
||||
# Vagrant #
|
||||
###########
|
||||
.vagrant
|
||||
|
||||
# App specific #
|
||||
################
|
||||
config.php
|
||||
data/files
|
53
sources/.scrutinizer.yml
Normal file
53
sources/.scrutinizer.yml
Normal file
|
@ -0,0 +1,53 @@
|
|||
filter:
|
||||
excluded_paths:
|
||||
- 'vendor/*'
|
||||
- 'tests/*'
|
||||
- 'app/Templates/*'
|
||||
paths: { }
|
||||
tools:
|
||||
php_sim:
|
||||
enabled: true
|
||||
min_mass: 16
|
||||
filter:
|
||||
excluded_paths:
|
||||
- 'vendor/*'
|
||||
- 'tests/*'
|
||||
- 'app/Templates/*'
|
||||
paths: { }
|
||||
php_pdepend:
|
||||
enabled: true
|
||||
configuration_file: null
|
||||
suffixes:
|
||||
- php
|
||||
excluded_dirs: { }
|
||||
filter:
|
||||
excluded_paths:
|
||||
- 'vendor/*'
|
||||
- 'tests/*'
|
||||
- 'app/Templates/*'
|
||||
paths: { }
|
||||
php_analyzer:
|
||||
enabled: true
|
||||
extensions:
|
||||
- php
|
||||
dependency_paths: { }
|
||||
filter:
|
||||
excluded_paths:
|
||||
- 'vendor/*'
|
||||
- 'tests/*'
|
||||
- 'app/Templates/*'
|
||||
paths: { }
|
||||
path_configs: { }
|
||||
php_changetracking:
|
||||
enabled: true
|
||||
bug_patterns:
|
||||
- '\bfix(?:es|ed)?\b'
|
||||
feature_patterns:
|
||||
- '\badd(?:s|ed)?\b'
|
||||
- '\bimplement(?:s|ed)?\b'
|
||||
filter:
|
||||
excluded_paths:
|
||||
- 'vendor/*'
|
||||
- 'tests/*'
|
||||
- 'app/Templates/*'
|
||||
paths: { }
|
10
sources/.travis.yml
Normal file
10
sources/.travis.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
language: php
|
||||
|
||||
php:
|
||||
- "5.6"
|
||||
- "5.5"
|
||||
- "5.4"
|
||||
- "5.3"
|
||||
|
||||
before_script: wget https://phar.phpunit.de/phpunit.phar
|
||||
script: php phpunit.phar
|
661
sources/LICENSE
Normal file
661
sources/LICENSE
Normal file
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
124
sources/README.markdown
Normal file
124
sources/README.markdown
Normal file
|
@ -0,0 +1,124 @@
|
|||
Kanboard
|
||||
========
|
||||
|
||||
Kanboard is a simple visual task board web application.
|
||||
|
||||
Official website: <http://kanboard.net>
|
||||
|
||||
- Inspired by the [Kanban methodology](http://en.wikipedia.org/wiki/Kanban)
|
||||
- Get a visual and clear overview of your project
|
||||
- Multiple boards with the ability to drag and drop tasks
|
||||
- Minimalist software, focus only on essential features (Less is more)
|
||||
- Open source and self-hosted
|
||||
- Super simple installation
|
||||
|
||||
[![Build Status](https://travis-ci.org/fguillot/kanboard.svg)](https://travis-ci.org/fguillot/kanboard)
|
||||
|
||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fguillot/kanboard/badges/quality-score.png?s=2b6490781608657cc8c43d02285bfafb4f489528)](https://scrutinizer-ci.com/g/fguillot/kanboard/)
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Multiple boards/projects
|
||||
- Boards customization, rename or add columns
|
||||
- Tasks with different colors, categories, sub-tasks, attachments, Markdown support for the description
|
||||
- Automatic actions
|
||||
- Users management with a basic privileges separation (administrator or regular user)
|
||||
- External authentication: Google and GitHub accounts as well as LDAP/ActiveDirectory
|
||||
- Webhooks to create tasks from an external software
|
||||
- Host anywhere (shared hosting, VPS, Raspberry Pi or localhost)
|
||||
- No external dependencies
|
||||
- **Super easy setup**, copy and paste files and you are done!
|
||||
- Translations in English, French, Brazilian Portuguese, Spanish, German, Polish, Swedish and Chinese
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
|
||||
Kanboard is under active development, have a look to the roadmap: <http://kanboard.net/#roadmap>
|
||||
|
||||
Known bugs
|
||||
----------
|
||||
|
||||
See Issues: <https://github.com/fguillot/kanboard/issues>
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
GNU Affero General Public License version 3: <http://www.gnu.org/licenses/agpl-3.0.txt>
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
Original author: [Frédéric Guillot](http://fredericguillot.com/)
|
||||
|
||||
Contributors:
|
||||
|
||||
- Alex Butum: https://github.com/dZkF9RWJT6wN8ux
|
||||
- Claudio Lobo
|
||||
- Gavlepeter: https://github.com/gavlepeter
|
||||
- Jesusaplsoft: https://github.com/jesusaplsoft
|
||||
- Kiswa: https://github.com/kiswa
|
||||
- Levlaz: https://github.com/levlaz
|
||||
- Mathgl67: https://github.com/mathgl67
|
||||
- Matthieu Keller: https://github.com/maggick
|
||||
- Maxime: https://github.com/EpocDotFr
|
||||
- Moraxy: https://github.com/moraxy
|
||||
- Nala Ginrut: https://github.com/NalaGinrut
|
||||
- Nekohayo: https://github.com/nekohayo
|
||||
- Olivier Maridat: https://github.com/oliviermaridat
|
||||
- Poikilotherm: https://github.com/poikilotherm
|
||||
- Raphaël Doursenaud: https://github.com/rdoursenaud
|
||||
- Rzeka: https://github.com/rzeka
|
||||
- Sebastien pacilly: https://github.com/spacilly
|
||||
- Toomyem: https://github.com/Toomyem
|
||||
- Troloo: https://github.com/troloo
|
||||
- Typz: https://github.com/Typz
|
||||
|
||||
There is also many people who have reported bugs or proposed awesome ideas.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
### Using Kanboard
|
||||
|
||||
- [Usage examples](docs/usage-examples.markdown)
|
||||
- [Manage users](docs/manage-users.markdown)
|
||||
- [Syntax guide](docs/syntax-guide.markdown)
|
||||
- [Automatic actions](docs/automatic-actions.markdown)
|
||||
|
||||
### Technical details
|
||||
|
||||
#### Installation
|
||||
|
||||
- [Installation instructions](docs/installation.markdown)
|
||||
- [Installation on Ubuntu](docs/ubuntu-installation.markdown)
|
||||
- [Installation on Debian](docs/debian-installation.markdown)
|
||||
- [Installation on Centos](docs/centos-installation.markdown)
|
||||
- [Upgrade Kanboard to a new version](docs/update.markdown)
|
||||
- [Secure connections (HTTPS)](docs/secure-connections.markdown)
|
||||
|
||||
#### Database
|
||||
|
||||
- [Sqlite database management](docs/sqlite-database.markdown)
|
||||
- [How to use Mysql](docs/mysql-configuration.markdown)
|
||||
- [How to use Postgresql](docs/postgresql-configuration.markdown)
|
||||
|
||||
#### Authentication
|
||||
|
||||
- [LDAP authentication](docs/ldap-authentication.markdown)
|
||||
- [Google authentication](docs/google-authentication.markdown)
|
||||
- [GitHub authentication](docs/github-authentication.markdown)
|
||||
|
||||
#### Developers
|
||||
|
||||
- [Json-RPC API](docs/api-json-rpc.markdown)
|
||||
- [How to use Kanboard with Vagrant](docs/vagrant.markdown)
|
||||
- [Webhooks](docs/webhooks.markdown)
|
||||
|
||||
The documentation is written in [Markdown](http://en.wikipedia.org/wiki/Markdown).
|
||||
If you want to improve the documentation, just send a pull-request.
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
Go to the official website: <http://kanboard.net/faq>
|
29
sources/Vagrantfile
vendored
Normal file
29
sources/Vagrantfile
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
$script = <<SCRIPT
|
||||
# install packages
|
||||
apt-get update
|
||||
apt-get install -y apache2 php5 php5-sqlite php5-ldap php5-xdebug
|
||||
service apache2 restart
|
||||
rm -f /var/www/html/index.html
|
||||
date > /etc/vagrant_provisioned_at
|
||||
echo "Go to http://localhost:8080/ (admin/admin) !"
|
||||
SCRIPT
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
# Image
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
|
||||
# Network
|
||||
config.vm.network :forwarded_port, guest: 80, host: 8080
|
||||
#config.vm.network "public_network", :bridge => "en0: Wi-Fi (AirPort)"
|
||||
|
||||
# Setup
|
||||
config.vm.provision "shell", inline: $script
|
||||
config.vm.synced_folder ".", "/var/www/html", owner: "www-data", group: "www-data"
|
||||
end
|
1
sources/app/.htaccess
Normal file
1
sources/app/.htaccess
Normal file
|
@ -0,0 +1 @@
|
|||
Deny from all
|
142
sources/app/Action/Base.php
Normal file
142
sources/app/Action/Base.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Core\Listener;
|
||||
|
||||
/**
|
||||
* Base class for automatic actions
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class Base implements Listener
|
||||
{
|
||||
/**
|
||||
* Project id
|
||||
*
|
||||
* @access private
|
||||
* @var integer
|
||||
*/
|
||||
private $project_id = 0;
|
||||
|
||||
/**
|
||||
* User parameters
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $params = array();
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @abstract
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
abstract public function doAction(array $data);
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @abstract
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getActionRequiredParameters();
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event (check if for the event data)
|
||||
*
|
||||
* @abstract
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getEventRequiredParameters();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
*/
|
||||
public function __construct($project_id)
|
||||
{
|
||||
$this->project_id = $project_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an user defined parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Parameter name
|
||||
* @param mixed $value Value
|
||||
*/
|
||||
public function setParam($name, $value)
|
||||
{
|
||||
$this->params[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an user defined parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Parameter name
|
||||
* @param mixed $default_value Default value
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParam($name, $default_value = null)
|
||||
{
|
||||
return isset($this->params[$name]) ? $this->params[$name] : $default_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an action is executable (right project and required parameters)
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action is executable
|
||||
*/
|
||||
public function isExecutable(array $data)
|
||||
{
|
||||
if (isset($data['project_id']) && $data['project_id'] == $this->project_id && $this->hasRequiredParameters($data)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event data has required parameters to execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if all keys are there
|
||||
*/
|
||||
public function hasRequiredParameters(array $data)
|
||||
{
|
||||
foreach ($this->getEventRequiredParameters() as $parameter) {
|
||||
if (! isset($data[$parameter])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function execute(array $data)
|
||||
{
|
||||
if ($this->isExecutable($data)) {
|
||||
return $this->doAction($data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
85
sources/app/Action/TaskAssignCategoryColor.php
Normal file
85
sources/app/Action/TaskAssignCategoryColor.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
|
||||
/**
|
||||
* Set a category automatically according to the color
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssignCategoryColor extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'color_id' => t('Color'),
|
||||
'category_id' => t('Category'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'color_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if ($data['color_id'] == $this->getParam('color_id')) {
|
||||
|
||||
$this->task->update(array(
|
||||
'id' => $data['task_id'],
|
||||
'category_id' => $this->getParam('category_id'),
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
85
sources/app/Action/TaskAssignColorCategory.php
Normal file
85
sources/app/Action/TaskAssignColorCategory.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
|
||||
/**
|
||||
* Assign a color to a specific category
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssignColorCategory extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'color_id' => t('Color'),
|
||||
'category_id' => t('Category'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'category_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if ($data['category_id'] == $this->getParam('category_id')) {
|
||||
|
||||
$this->task->update(array(
|
||||
'id' => $data['task_id'],
|
||||
'color_id' => $this->getParam('color_id'),
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
85
sources/app/Action/TaskAssignColorUser.php
Normal file
85
sources/app/Action/TaskAssignColorUser.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
|
||||
/**
|
||||
* Assign a color to a specific user
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssignColorUser extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'color_id' => t('Color'),
|
||||
'user_id' => t('Assignee'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'owner_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if ($data['owner_id'] == $this->getParam('user_id')) {
|
||||
|
||||
$this->task->update(array(
|
||||
'id' => $data['task_id'],
|
||||
'color_id' => $this->getParam('color_id'),
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
95
sources/app/Action/TaskAssignCurrentUser.php
Normal file
95
sources/app/Action/TaskAssignCurrentUser.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
use Model\Acl;
|
||||
|
||||
/**
|
||||
* Assign a task to the logged user
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssignCurrentUser extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Acl model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Acl
|
||||
*/
|
||||
private $acl;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
* @param \Model\Acl $acl Acl model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task, Acl $acl)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->task = $task;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'column_id' => t('Column'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if ($data['column_id'] == $this->getParam('column_id')) {
|
||||
|
||||
$this->task->update(array(
|
||||
'id' => $data['task_id'],
|
||||
'owner_id' => $this->acl->getUserId(),
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
85
sources/app/Action/TaskAssignSpecificUser.php
Normal file
85
sources/app/Action/TaskAssignSpecificUser.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
|
||||
/**
|
||||
* Assign a task to a specific user
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssignSpecificUser extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'column_id' => t('Column'),
|
||||
'user_id' => t('Assignee'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if ($data['column_id'] == $this->getParam('column_id')) {
|
||||
|
||||
$this->task->update(array(
|
||||
'id' => $data['task_id'],
|
||||
'owner_id' => $this->getParam('user_id'),
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
79
sources/app/Action/TaskClose.php
Normal file
79
sources/app/Action/TaskClose.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
|
||||
/**
|
||||
* Close automatically a task
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskClose extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'column_id' => t('Column'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if ($data['column_id'] == $this->getParam('column_id')) {
|
||||
$this->task->close($data['task_id']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
83
sources/app/Action/TaskDuplicateAnotherProject.php
Normal file
83
sources/app/Action/TaskDuplicateAnotherProject.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
|
||||
/**
|
||||
* Duplicate a task to another project
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskDuplicateAnotherProject extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the action (defined by the user)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getActionRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'column_id' => t('Column'),
|
||||
'project_id' => t('Project'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required parameter for the event
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEventRequiredParameters()
|
||||
{
|
||||
return array(
|
||||
'task_id',
|
||||
'column_id',
|
||||
'project_id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function doAction(array $data)
|
||||
{
|
||||
if ($data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id')) {
|
||||
|
||||
$this->task->duplicateToAnotherProject($data['task_id'], $this->getParam('project_id'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
143
sources/app/Controller/Action.php
Normal file
143
sources/app/Controller/Action.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
/**
|
||||
* Automatic actions management
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Action extends Base
|
||||
{
|
||||
/**
|
||||
* List of automatic actions for a given project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('action_index', array(
|
||||
'values' => array('project_id' => $project['id']),
|
||||
'project' => $project,
|
||||
'actions' => $this->action->getAllByProject($project['id']),
|
||||
'available_actions' => $this->action->getAvailableActions(),
|
||||
'available_events' => $this->action->getAvailableEvents(),
|
||||
'available_params' => $this->action->getAllActionParameters(),
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->project->getUsersList($project['id']),
|
||||
'projects_list' => $this->project->getList(false),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'menu' => 'projects',
|
||||
'title' => t('Automatic actions')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define action parameters (step 2)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function params()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$values = $this->request->getValues();
|
||||
$action = $this->action->load($values['action_name'], $values['project_id']);
|
||||
|
||||
$this->response->html($this->template->layout('action_params', array(
|
||||
'values' => $values,
|
||||
'action_params' => $action->getActionRequiredParameters(),
|
||||
'columns_list' => $this->board->getColumnsList($project['id']),
|
||||
'users_list' => $this->project->getUsersList($project['id']),
|
||||
'projects_list' => $this->project->getList(false),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'categories_list' => $this->category->getList($project['id']),
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Automatic actions')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new action (last step)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid,) = $this->action->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->action->create($values)) {
|
||||
$this->session->flash(t('Your automatic action have been created successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your automatic action.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing an action
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$this->response->html($this->template->layout('action_remove', array(
|
||||
'action' => $this->action->getById($this->request->getIntegerParam('action_id')),
|
||||
'available_events' => $this->action->getAvailableEvents(),
|
||||
'available_actions' => $this->action->getAvailableActions(),
|
||||
'menu' => 'projects',
|
||||
'title' => t('Remove an action')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an action
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$action = $this->action->getById($this->request->getIntegerParam('action_id'));
|
||||
|
||||
if ($action && $this->action->remove($action['id'])) {
|
||||
$this->session->flash(t('Action removed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to remove this action.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=action&action=index&project_id='.$action['project_id']);
|
||||
}
|
||||
}
|
29
sources/app/Controller/App.php
Normal file
29
sources/app/Controller/App.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Model\Project as ProjectModel;
|
||||
|
||||
/**
|
||||
* Application controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class App extends Base
|
||||
{
|
||||
/**
|
||||
* Redirect to the project creation page or the board controller
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->project->countByStatus(ProjectModel::ACTIVE)) {
|
||||
$this->response->redirect('?controller=board');
|
||||
}
|
||||
else {
|
||||
$this->redirectNoProject();
|
||||
}
|
||||
}
|
||||
}
|
249
sources/app/Controller/Base.php
Normal file
249
sources/app/Controller/Base.php
Normal file
|
@ -0,0 +1,249 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Core\Registry;
|
||||
use Core\Security;
|
||||
use Core\Translator;
|
||||
use Model\LastLogin;
|
||||
|
||||
/**
|
||||
* Base controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
* @property \Model\Acl $acl
|
||||
* @property \Model\Action $action
|
||||
* @property \Model\Board $board
|
||||
* @property \Model\Category $category
|
||||
* @property \Model\Comment $comment
|
||||
* @property \Model\Config $config
|
||||
* @property \Model\File $file
|
||||
* @property \Model\Google $google
|
||||
* @property \Model\GitHub $gitHub
|
||||
* @property \Model\LastLogin $lastLogin
|
||||
* @property \Model\Ldap $ldap
|
||||
* @property \Model\Project $project
|
||||
* @property \Model\RememberMe $rememberMe
|
||||
* @property \Model\SubTask $subTask
|
||||
* @property \Model\Task $task
|
||||
* @property \Model\User $user
|
||||
*/
|
||||
abstract class Base
|
||||
{
|
||||
/**
|
||||
* Request instance
|
||||
*
|
||||
* @accesss public
|
||||
* @var \Core\Request
|
||||
*/
|
||||
public $request;
|
||||
|
||||
/**
|
||||
* Response instance
|
||||
*
|
||||
* @accesss public
|
||||
* @var \Core\Response
|
||||
*/
|
||||
public $response;
|
||||
|
||||
/**
|
||||
* Template instance
|
||||
*
|
||||
* @accesss public
|
||||
* @var \Core\Template
|
||||
*/
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* Session instance
|
||||
*
|
||||
* @accesss public
|
||||
* @var \Core\Session
|
||||
*/
|
||||
public $session;
|
||||
|
||||
/**
|
||||
* Registry instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Core\Registry
|
||||
*/
|
||||
private $registry;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Core\Registry $registry Registry instance
|
||||
*/
|
||||
public function __construct(Registry $registry)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load automatically models
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Model name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
$class = '\Model\\'.ucfirst($name);
|
||||
$this->registry->$name = new $class($this->registry->shared('db'), $this->registry->shared('event'));
|
||||
return $this->registry->shared($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed before each action
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeAction($controller, $action)
|
||||
{
|
||||
// Start the session
|
||||
$this->session->open(BASE_URL_DIRECTORY, SESSION_SAVE_PATH);
|
||||
|
||||
// HTTP secure headers
|
||||
$this->response->csp(array('style-src' => "'self' 'unsafe-inline'"));
|
||||
$this->response->nosniff();
|
||||
$this->response->xss();
|
||||
$this->response->hsts();
|
||||
$this->response->xframe();
|
||||
|
||||
// Load translations
|
||||
$language = $this->config->get('language', 'en_US');
|
||||
if ($language !== 'en_US') Translator::load($language);
|
||||
|
||||
// Set timezone
|
||||
date_default_timezone_set($this->config->get('timezone', 'UTC'));
|
||||
|
||||
// Authentication
|
||||
if (! $this->acl->isLogged() && ! $this->acl->isPublicAction($controller, $action)) {
|
||||
|
||||
// Try the remember me authentication first
|
||||
if (! $this->rememberMe->authenticate()) {
|
||||
|
||||
// Redirect to the login form if not authenticated
|
||||
$this->response->redirect('?controller=user&action=login');
|
||||
}
|
||||
else {
|
||||
|
||||
$this->lastLogin->create(
|
||||
LastLogin::AUTH_REMEMBER_ME,
|
||||
$this->acl->getUserId(),
|
||||
$this->user->getIpAddress(),
|
||||
$this->user->getUserAgent()
|
||||
);
|
||||
}
|
||||
}
|
||||
else if ($this->rememberMe->hasCookie()) {
|
||||
$this->rememberMe->refresh();
|
||||
}
|
||||
|
||||
// Check if the user is allowed to see this page
|
||||
if (! $this->acl->isPageAccessAllowed($controller, $action)) {
|
||||
$this->response->redirect('?controller=user&action=forbidden');
|
||||
}
|
||||
|
||||
// Attach events
|
||||
$this->action->attachEvents();
|
||||
$this->project->attachEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Application not found page (404 error)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function notfound()
|
||||
{
|
||||
$this->response->html($this->template->layout('app_notfound', array('title' => t('Page not found'))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Application forbidden page
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function forbidden()
|
||||
{
|
||||
$this->response->html($this->template->layout('app_forbidden', array('title' => t('Access Forbidden'))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the CSRF token from the URL is correct
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
protected function checkCSRFParam()
|
||||
{
|
||||
if (! Security::validateCSRFToken($this->request->getStringParam('csrf_token'))) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user have access to the given project
|
||||
*
|
||||
* @access protected
|
||||
* @param integer $project_id Project id
|
||||
*/
|
||||
protected function checkProjectPermissions($project_id)
|
||||
{
|
||||
if ($this->acl->isRegularUser()) {
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirection when there is no project in the database
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
protected function redirectNoProject()
|
||||
{
|
||||
$this->session->flash(t('There is no active project, the first step is to create a new project.'));
|
||||
$this->response->redirect('?controller=project&action=create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for task views
|
||||
*
|
||||
* @access protected
|
||||
* @param string $template Template name
|
||||
* @param array $params Template parameters
|
||||
* @return string
|
||||
*/
|
||||
protected function taskLayout($template, array $params)
|
||||
{
|
||||
$content = $this->template->load($template, $params);
|
||||
$params['task_content_for_layout'] = $content;
|
||||
|
||||
return $this->template->layout('task_layout', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common method to get a task for task views
|
||||
*
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function getTask()
|
||||
{
|
||||
$task = $this->task->getById($this->request->getIntegerParam('task_id'), true);
|
||||
|
||||
if (! $task) {
|
||||
$this->notfound();
|
||||
}
|
||||
|
||||
$this->checkProjectPermissions($task['project_id']);
|
||||
|
||||
return $task;
|
||||
}
|
||||
}
|
426
sources/app/Controller/Board.php
Normal file
426
sources/app/Controller/Board.php
Normal file
|
@ -0,0 +1,426 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Model\Project as ProjectModel;
|
||||
use Model\User as UserModel;
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* Board controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Board extends Base
|
||||
{
|
||||
/**
|
||||
* Move a column up
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function moveUp()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$column_id = $this->request->getIntegerParam('column_id');
|
||||
|
||||
$this->board->moveUp($project_id, $column_id);
|
||||
|
||||
$this->response->redirect('?controller=board&action=edit&project_id='.$project_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a column down
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function moveDown()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$column_id = $this->request->getIntegerParam('column_id');
|
||||
|
||||
$this->board->moveDown($project_id, $column_id);
|
||||
|
||||
$this->response->redirect('?controller=board&action=edit&project_id='.$project_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a task assignee directly from the board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function assign()
|
||||
{
|
||||
$task = $this->task->getById($this->request->getIntegerParam('task_id'));
|
||||
$project = $this->project->getById($task['project_id']);
|
||||
$projects = $this->project->getListByStatus(ProjectModel::ACTIVE);
|
||||
|
||||
if ($this->acl->isRegularUser()) {
|
||||
$projects = $this->project->filterListByAccess($projects, $this->acl->getUserId());
|
||||
}
|
||||
|
||||
if (! $project) $this->notfound();
|
||||
$this->checkProjectPermissions($project['id']);
|
||||
|
||||
if ($this->request->isAjax()) {
|
||||
|
||||
$this->response->html($this->template->load('board_assign', array(
|
||||
'errors' => array(),
|
||||
'values' => $task,
|
||||
'users_list' => $this->project->getUsersList($project['id']),
|
||||
'projects' => $projects,
|
||||
'current_project_id' => $project['id'],
|
||||
'current_project_name' => $project['name'],
|
||||
)));
|
||||
}
|
||||
else {
|
||||
|
||||
$this->response->html($this->template->layout('board_assign', array(
|
||||
'errors' => array(),
|
||||
'values' => $task,
|
||||
'users_list' => $this->project->getUsersList($project['id']),
|
||||
'projects' => $projects,
|
||||
'current_project_id' => $project['id'],
|
||||
'current_project_name' => $project['name'],
|
||||
'menu' => 'boards',
|
||||
'title' => t('Change assignee').' - '.$task['title'],
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an assignee modification
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function assignTask()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
$this->checkProjectPermissions($values['project_id']);
|
||||
|
||||
list($valid,) = $this->task->validateAssigneeModification($values);
|
||||
|
||||
if ($valid && $this->task->update($values)) {
|
||||
$this->session->flash(t('Task updated successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=board&action=show&project_id='.$values['project_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the public version of a board
|
||||
* Access checked by a simple token, no user login, read only, auto-refresh
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function readonly()
|
||||
{
|
||||
$token = $this->request->getStringParam('token');
|
||||
$project = $this->project->getByToken($token);
|
||||
|
||||
// Token verification
|
||||
if (! $project) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
// Display the board with a specific layout
|
||||
$this->response->html($this->template->layout('board_public', array(
|
||||
'project' => $project,
|
||||
'columns' => $this->board->get($project['id']),
|
||||
'categories' => $this->category->getList($project['id'], false),
|
||||
'title' => $project['name'],
|
||||
'no_layout' => true,
|
||||
'auto_refresh' => true,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user to the default project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$projects = $this->project->getListByStatus(ProjectModel::ACTIVE);
|
||||
$project_id = 0;
|
||||
$project_name = '';
|
||||
|
||||
if ($this->acl->isRegularUser()) {
|
||||
$projects = $this->project->filterListByAccess($projects, $this->acl->getUserId());
|
||||
}
|
||||
|
||||
if (empty($projects)) {
|
||||
|
||||
if ($this->acl->isAdminUser()) {
|
||||
$this->redirectNoProject();
|
||||
}
|
||||
else {
|
||||
$this->response->redirect('?controller=project&action=forbidden');
|
||||
}
|
||||
}
|
||||
else if (! empty($_SESSION['user']['default_project_id']) && isset($projects[$_SESSION['user']['default_project_id']])) {
|
||||
$project_id = $_SESSION['user']['default_project_id'];
|
||||
$project_name = $projects[$_SESSION['user']['default_project_id']];
|
||||
}
|
||||
else {
|
||||
list($project_id, $project_name) = each($projects);
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=board&action=show&project_id='.$project_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a board for a given project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$user_id = $this->request->getIntegerParam('user_id', UserModel::EVERYBODY_ID);
|
||||
|
||||
$this->checkProjectPermissions($project_id);
|
||||
$projects = $this->project->getAvailableList($this->acl->getUserId());
|
||||
|
||||
if (! isset($projects[$project_id])) {
|
||||
$this->notfound();
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('board_index', array(
|
||||
'users' => $this->project->getUsersList($project_id, true, true),
|
||||
'filters' => array('user_id' => $user_id),
|
||||
'projects' => $projects,
|
||||
'current_project_id' => $project_id,
|
||||
'current_project_name' => $projects[$project_id],
|
||||
'board' => $this->board->get($project_id),
|
||||
'categories' => $this->category->getList($project_id, true, true),
|
||||
'menu' => 'boards',
|
||||
'title' => $projects[$project_id],
|
||||
'board_selector' => $projects,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to edit a board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) $this->notfound();
|
||||
|
||||
$columns = $this->board->getColumns($project_id);
|
||||
$values = array();
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$values['title['.$column['id'].']'] = $column['title'];
|
||||
$values['task_limit['.$column['id'].']'] = $column['task_limit'] ?: null;
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('board_edit', array(
|
||||
'errors' => array(),
|
||||
'values' => $values + array('project_id' => $project_id),
|
||||
'columns' => $columns,
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Edit board')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and update a board
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) $this->notfound();
|
||||
|
||||
$columns = $this->board->getColumns($project_id);
|
||||
$data = $this->request->getValues();
|
||||
$values = $columns_list = array();
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$columns_list[$column['id']] = $column['title'];
|
||||
$values['title['.$column['id'].']'] = isset($data['title'][$column['id']]) ? $data['title'][$column['id']] : '';
|
||||
$values['task_limit['.$column['id'].']'] = isset($data['task_limit'][$column['id']]) ? $data['task_limit'][$column['id']] : 0;
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->board->validateModification($columns_list, $values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->board->update($data)) {
|
||||
$this->session->flash(t('Board updated successfully.'));
|
||||
$this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update this board.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('board_edit', array(
|
||||
'errors' => $errors,
|
||||
'values' => $values + array('project_id' => $project_id),
|
||||
'columns' => $columns,
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Edit board')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and add a new column
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) $this->notfound();
|
||||
|
||||
$columns = $this->board->getColumnsList($project_id);
|
||||
$data = $this->request->getValues();
|
||||
$values = array();
|
||||
|
||||
foreach ($columns as $column_id => $column_title) {
|
||||
$values['title['.$column_id.']'] = $column_title;
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->board->validateCreation($data);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->board->add($data)) {
|
||||
$this->session->flash(t('Board updated successfully.'));
|
||||
$this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update this board.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('board_edit', array(
|
||||
'errors' => $errors,
|
||||
'values' => $values + $data,
|
||||
'columns' => $columns,
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Edit board')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing a column
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$this->response->html($this->template->layout('board_remove', array(
|
||||
'column' => $this->board->getColumn($this->request->getIntegerParam('column_id')),
|
||||
'menu' => 'projects',
|
||||
'title' => t('Remove a column from a board')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a column
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
|
||||
|
||||
if ($column && $this->board->removeColumn($column['id'])) {
|
||||
$this->session->flash(t('Column removed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to remove this column.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=board&action=edit&project_id='.$column['project_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the board (Ajax request made by the drag and drop)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
if (isset($values['positions'])) {
|
||||
$this->board->saveTasksPosition($values['positions']);
|
||||
}
|
||||
|
||||
$this->response->html(
|
||||
$this->template->load('board_show', array(
|
||||
'current_project_id' => $project_id,
|
||||
'board' => $this->board->get($project_id),
|
||||
'categories' => $this->category->getList($project_id, false),
|
||||
)),
|
||||
201
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->response->status(401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the board have been changed
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$timestamp = $this->request->getIntegerParam('timestamp');
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
if ($this->project->isModifiedSince($project_id, $timestamp)) {
|
||||
$this->response->html(
|
||||
$this->template->load('board_show', array(
|
||||
'current_project_id' => $project_id,
|
||||
'board' => $this->board->get($project_id),
|
||||
'categories' => $this->category->getList($project_id, false),
|
||||
))
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->response->status(304);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->response->status(401);
|
||||
}
|
||||
}
|
||||
}
|
191
sources/app/Controller/Category.php
Normal file
191
sources/app/Controller/Category.php
Normal file
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
/**
|
||||
* Categories management
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Category extends Base
|
||||
{
|
||||
/**
|
||||
* Get the current project (common method between actions)
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getProject()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the category (common method between actions)
|
||||
*
|
||||
* @access private
|
||||
* @param $project_id
|
||||
* @return array
|
||||
*/
|
||||
private function getCategory($project_id)
|
||||
{
|
||||
$category = $this->category->getById($this->request->getIntegerParam('category_id'));
|
||||
|
||||
if (! $category) {
|
||||
$this->session->flashError(t('Category not found.'));
|
||||
$this->response->redirect('?controller=category&action=index&project_id='.$project_id);
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of categories for a given project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->template->layout('category_index', array(
|
||||
'categories' => $this->category->getList($project['id'], false),
|
||||
'values' => array('project_id' => $project['id']),
|
||||
'errors' => array(),
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Categories')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->category->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->category->create($values)) {
|
||||
$this->session->flash(t('Your category have been created successfully.'));
|
||||
$this->response->redirect('?controller=category&action=index&project_id='.$project['id']);
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your category.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('category_index', array(
|
||||
'categories' => $this->category->getList($project['id'], false),
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Categories')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a category (display the form)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$category = $this->getCategory($project['id']);
|
||||
|
||||
$this->response->html($this->template->layout('category_edit', array(
|
||||
'values' => $category,
|
||||
'errors' => array(),
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Categories')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a category (validate the form and update the database)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->category->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->category->update($values)) {
|
||||
$this->session->flash(t('Your category have been updated successfully.'));
|
||||
$this->response->redirect('?controller=category&action=index&project_id='.$project['id']);
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your category.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('category_edit', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Categories')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing a category
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$category = $this->getCategory($project['id']);
|
||||
|
||||
$this->response->html($this->template->layout('category_remove', array(
|
||||
'project' => $project,
|
||||
'category' => $category,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Remove a category')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a category
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project = $this->getProject();
|
||||
$category = $this->getCategory($project['id']);
|
||||
|
||||
if ($this->category->remove($category['id'])) {
|
||||
$this->session->flash(t('Category removed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to remove this category.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=category&action=index&project_id='.$project['id']);
|
||||
}
|
||||
}
|
194
sources/app/Controller/Comment.php
Normal file
194
sources/app/Controller/Comment.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
/**
|
||||
* Comment controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Comment extends Base
|
||||
{
|
||||
/**
|
||||
* Get the current comment
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getComment()
|
||||
{
|
||||
$comment = $this->comment->getById($this->request->getIntegerParam('comment_id'));
|
||||
|
||||
if (! $comment) {
|
||||
$this->notfound();
|
||||
}
|
||||
|
||||
if (! $this->acl->isAdminUser() && $comment['user_id'] != $this->acl->getUserId()) {
|
||||
$this->forbidden();
|
||||
}
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbidden page for comments
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function forbidden()
|
||||
{
|
||||
$this->response->html($this->template->layout('comment_forbidden', array(
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Access Forbidden')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add comment form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('comment_create', array(
|
||||
'values' => array(
|
||||
'user_id' => $this->acl->getUserId(),
|
||||
'task_id' => $task['id'],
|
||||
),
|
||||
'errors' => array(),
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Add a comment')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->comment->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->comment->create($values)) {
|
||||
$this->session->flash(t('Comment added successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your comment.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comments');
|
||||
}
|
||||
|
||||
$this->response->html($this->taskLayout('comment_create', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Add a comment')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a comment
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$comment = $this->getComment();
|
||||
|
||||
$this->response->html($this->taskLayout('comment_edit', array(
|
||||
'values' => $comment,
|
||||
'errors' => array(),
|
||||
'comment' => $comment,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a comment')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update and validate a comment
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$comment = $this->getComment();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->comment->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->comment->update($values)) {
|
||||
$this->session->flash(t('Comment updated successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your comment.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comment-'.$comment['id']);
|
||||
}
|
||||
|
||||
$this->response->html($this->taskLayout('comment_edit', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'comment' => $comment,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a comment')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing a comment
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$comment = $this->getComment();
|
||||
|
||||
$this->response->html($this->taskLayout('comment_remove', array(
|
||||
'comment' => $comment,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Remove a comment')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a comment
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
$comment = $this->getComment();
|
||||
|
||||
if ($this->comment->remove($comment['id'])) {
|
||||
$this->session->flash(t('Comment removed successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to remove this comment.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comments');
|
||||
}
|
||||
}
|
144
sources/app/Controller/File.php
Normal file
144
sources/app/Controller/File.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Model\File as FileModel;
|
||||
|
||||
/**
|
||||
* File controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class File extends Base
|
||||
{
|
||||
/**
|
||||
* File upload form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('file_new', array(
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'max_size' => ini_get('upload_max_filesize'),
|
||||
'title' => t('Attach a document')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* File upload (save files)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($this->file->upload($task['project_id'], $task['id'], 'files') === true) {
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#attachments');
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to upload the file.'));
|
||||
$this->response->redirect('?controller=file&action=create&task_id='.$task['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* File download
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function download()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
|
||||
$filename = FileModel::BASE_PATH.$file['path'];
|
||||
|
||||
if ($file['task_id'] == $task['id'] && file_exists($filename)) {
|
||||
$this->response->forceDownload($file['name']);
|
||||
$this->response->binary(file_get_contents($filename));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a file (show the content in a popover)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function open()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
|
||||
|
||||
if ($file['task_id'] == $task['id']) {
|
||||
$this->response->html($this->template->load('file_open', array(
|
||||
'file' => $file
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file content (work only for images)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function image()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
|
||||
$filename = FileModel::BASE_PATH.$file['path'];
|
||||
|
||||
if ($file['task_id'] == $task['id'] && file_exists($filename)) {
|
||||
$metadata = getimagesize($filename);
|
||||
|
||||
if (isset($metadata['mime'])) {
|
||||
$this->response->contentType($metadata['mime']);
|
||||
readfile($filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a file
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
|
||||
|
||||
if ($file['task_id'] == $task['id'] && $this->file->remove($file['id'])) {
|
||||
$this->session->flash(t('File removed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to remove this file.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing a file
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
|
||||
|
||||
$this->response->html($this->taskLayout('file_remove', array(
|
||||
'task' => $task,
|
||||
'file' => $file,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Remove a file')
|
||||
)));
|
||||
}
|
||||
}
|
367
sources/app/Controller/Project.php
Normal file
367
sources/app/Controller/Project.php
Normal file
|
@ -0,0 +1,367 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
* Project controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Project extends Base
|
||||
{
|
||||
/**
|
||||
* Task search for a given project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function search()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$search = $this->request->getStringParam('search');
|
||||
|
||||
$project = $this->project->getById($project_id);
|
||||
$tasks = array();
|
||||
$nb_tasks = 0;
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$this->checkProjectPermissions($project['id']);
|
||||
|
||||
if ($search !== '') {
|
||||
|
||||
$filters = array(
|
||||
array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id),
|
||||
'or' => array(
|
||||
array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'),
|
||||
//array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'),
|
||||
)
|
||||
);
|
||||
|
||||
$tasks = $this->task->find($filters);
|
||||
$nb_tasks = count($tasks);
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('project_search', array(
|
||||
'tasks' => $tasks,
|
||||
'nb_tasks' => $nb_tasks,
|
||||
'values' => array(
|
||||
'search' => $search,
|
||||
'controller' => 'project',
|
||||
'action' => 'search',
|
||||
'project_id' => $project['id'],
|
||||
),
|
||||
'menu' => 'projects',
|
||||
'project' => $project,
|
||||
'columns' => $this->board->getColumnsList($project_id),
|
||||
'categories' => $this->category->getList($project['id'], false),
|
||||
'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* List of completed tasks for a given project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function tasks()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$project = $this->project->getById($project_id);
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$this->checkProjectPermissions($project['id']);
|
||||
|
||||
$filters = array(
|
||||
array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id),
|
||||
array('column' => 'is_active', 'operator' => 'eq', 'value' => TaskModel::STATUS_CLOSED),
|
||||
);
|
||||
|
||||
$tasks = $this->task->find($filters);
|
||||
$nb_tasks = count($tasks);
|
||||
|
||||
$this->response->html($this->template->layout('project_tasks', array(
|
||||
'menu' => 'projects',
|
||||
'project' => $project,
|
||||
'columns' => $this->board->getColumnsList($project_id),
|
||||
'categories' => $this->category->getList($project['id'], false),
|
||||
'tasks' => $tasks,
|
||||
'nb_tasks' => $nb_tasks,
|
||||
'title' => $project['name'].' ('.$nb_tasks.')'
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* List of projects
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$projects = $this->project->getAll(true, $this->acl->isRegularUser());
|
||||
$nb_projects = count($projects);
|
||||
|
||||
$this->response->html($this->template->layout('project_index', array(
|
||||
'projects' => $projects,
|
||||
'nb_projects' => $nb_projects,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Projects').' ('.$nb_projects.')'
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a new project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->response->html($this->template->layout('project_new', array(
|
||||
'errors' => array(),
|
||||
'values' => array(),
|
||||
'menu' => 'projects',
|
||||
'title' => t('New project')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->project->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->project->create($values)) {
|
||||
$this->session->flash(t('Your project have been created successfully.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('project_new', array(
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'menu' => 'projects',
|
||||
'title' => t('New Project')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to edit a project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$project = $this->project->getById($this->request->getIntegerParam('project_id'));
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('project_edit', array(
|
||||
'errors' => array(),
|
||||
'values' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Edit project')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and update a project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$values = $this->request->getValues() + array('is_active' => 0);
|
||||
list($valid, $errors) = $this->project->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->project->update($values)) {
|
||||
$this->session->flash(t('Project updated successfully.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('project_edit', array(
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Edit Project')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before to remove a project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$project = $this->project->getById($this->request->getIntegerParam('project_id'));
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('project_remove', array(
|
||||
'project' => $project,
|
||||
'menu' => 'projects',
|
||||
'title' => t('Remove project')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
|
||||
if ($project_id && $this->project->remove($project_id)) {
|
||||
$this->session->flash(t('Project removed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to remove this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
|
||||
if ($project_id && $this->project->enable($project_id)) {
|
||||
$this->session->flash(t('Project activated successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to activate this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
|
||||
if ($project_id && $this->project->disable($project_id)) {
|
||||
$this->session->flash(t('Project disabled successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to disable this project.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Users list for the selected project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
$project = $this->project->getById($this->request->getIntegerParam('project_id'));
|
||||
|
||||
if (! $project) {
|
||||
$this->session->flashError(t('Project not found.'));
|
||||
$this->response->redirect('?controller=project');
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('project_users', array(
|
||||
'project' => $project,
|
||||
'users' => $this->project->getAllUsers($project['id']),
|
||||
'menu' => 'projects',
|
||||
'title' => t('Edit project access list')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow a specific user for the selected project
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function allow()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid,) = $this->project->validateUserAccess($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->project->allowUser($values['project_id'], $values['user_id'])) {
|
||||
$this->session->flash(t('Project updated successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke user access
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function revoke()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
);
|
||||
|
||||
list($valid,) = $this->project->validateUserAccess($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->project->revokeUser($values['project_id'], $values['user_id'])) {
|
||||
$this->session->flash(t('Project updated successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update this project.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
|
||||
}
|
||||
}
|
186
sources/app/Controller/Subtask.php
Normal file
186
sources/app/Controller/Subtask.php
Normal file
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
/**
|
||||
* SubTask controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Subtask extends Base
|
||||
{
|
||||
/**
|
||||
* Get the current subtask
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getSubtask()
|
||||
{
|
||||
$subtask = $this->subTask->getById($this->request->getIntegerParam('subtask_id'));
|
||||
|
||||
if (! $subtask) {
|
||||
$this->notfound();
|
||||
}
|
||||
|
||||
return $subtask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_create', array(
|
||||
'values' => array(
|
||||
'task_id' => $task['id'],
|
||||
),
|
||||
'errors' => array(),
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Add a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation and creation
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->subTask->validate($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->subTask->create($values)) {
|
||||
$this->session->flash(t('Sub-task added successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your sub-task.'));
|
||||
}
|
||||
|
||||
if (isset($values['another_subtask']) && $values['another_subtask'] == 1) {
|
||||
$this->response->redirect('?controller=subtask&action=create&task_id='.$task['id']);
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
|
||||
}
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_create', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Add a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubTask();
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_edit', array(
|
||||
'values' => $subtask,
|
||||
'errors' => array(),
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'status_list' => $this->subTask->getStatusList(),
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update and validate a subtask
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubtask();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->subTask->validate($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->subTask->update($values)) {
|
||||
$this->session->flash(t('Sub-task updated successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your sub-task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
|
||||
}
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_edit', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'status_list' => $this->subTask->getStatusList(),
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing a subtask
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubtask();
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_remove', array(
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Remove a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a subtask
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubtask();
|
||||
|
||||
if ($this->subTask->remove($subtask['id'])) {
|
||||
$this->session->flash(t('Sub-task removed successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to remove this sub-task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
|
||||
}
|
||||
}
|
428
sources/app/Controller/Task.php
Normal file
428
sources/app/Controller/Task.php
Normal file
|
@ -0,0 +1,428 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
use Model\Project as ProjectModel;
|
||||
|
||||
/**
|
||||
* Task controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Task extends Base
|
||||
{
|
||||
/**
|
||||
* Webhook to create a task (useful for external software)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$token = $this->request->getStringParam('token');
|
||||
|
||||
if ($this->config->get('webhooks_token') !== $token) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
$defaultProject = $this->project->getFirst();
|
||||
|
||||
$values = array(
|
||||
'title' => $this->request->getStringParam('title'),
|
||||
'description' => $this->request->getStringParam('description'),
|
||||
'color_id' => $this->request->getStringParam('color_id', 'blue'),
|
||||
'project_id' => $this->request->getIntegerParam('project_id', $defaultProject['id']),
|
||||
'owner_id' => $this->request->getIntegerParam('owner_id'),
|
||||
'column_id' => $this->request->getIntegerParam('column_id'),
|
||||
'category_id' => $this->request->getIntegerParam('category_id'),
|
||||
);
|
||||
|
||||
if ($values['column_id'] == 0) {
|
||||
$values['column_id'] = $this->board->getFirstColumn($values['project_id']);
|
||||
}
|
||||
|
||||
list($valid,) = $this->task->validateCreation($values);
|
||||
|
||||
if ($valid && $this->task->create($values)) {
|
||||
$this->response->text('OK');
|
||||
}
|
||||
|
||||
$this->response->text('FAILED');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('task_show', array(
|
||||
'files' => $this->file->getAll($task['id']),
|
||||
'comments' => $this->comment->getAll($task['id']),
|
||||
'subtasks' => $this->subTask->getAll($task['id']),
|
||||
'task' => $task,
|
||||
'columns_list' => $this->board->getColumnsList($task['project_id']),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'menu' => 'tasks',
|
||||
'title' => $task['title'],
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a new task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$this->checkProjectPermissions($project_id);
|
||||
|
||||
$this->response->html($this->template->layout('task_new', array(
|
||||
'errors' => array(),
|
||||
'values' => array(
|
||||
'project_id' => $project_id,
|
||||
'column_id' => $this->request->getIntegerParam('column_id'),
|
||||
'color_id' => $this->request->getStringParam('color_id'),
|
||||
'owner_id' => $this->request->getIntegerParam('owner_id'),
|
||||
'another_task' => $this->request->getIntegerParam('another_task'),
|
||||
),
|
||||
'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE),
|
||||
'columns_list' => $this->board->getColumnsList($project_id),
|
||||
'users_list' => $this->project->getUsersList($project_id),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'categories_list' => $this->category->getList($project_id),
|
||||
'menu' => 'tasks',
|
||||
'title' => t('New task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
$values['creator_id'] = $this->acl->getUserId();
|
||||
|
||||
$this->checkProjectPermissions($values['project_id']);
|
||||
|
||||
list($valid, $errors) = $this->task->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->task->create($values)) {
|
||||
$this->session->flash(t('Task created successfully.'));
|
||||
|
||||
if (isset($values['another_task']) && $values['another_task'] == 1) {
|
||||
unset($values['title']);
|
||||
unset($values['description']);
|
||||
$this->response->redirect('?controller=task&action=create&'.http_build_query($values));
|
||||
}
|
||||
else {
|
||||
$this->response->redirect('?controller=board&action=show&project_id='.$values['project_id']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your task.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('task_new', array(
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE),
|
||||
'columns_list' => $this->board->getColumnsList($values['project_id']),
|
||||
'users_list' => $this->project->getUsersList($values['project_id']),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'categories_list' => $this->category->getList($values['project_id']),
|
||||
'menu' => 'tasks',
|
||||
'title' => t('New task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to edit a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
if (! empty($task['date_due'])) {
|
||||
$task['date_due'] = date(t('m/d/Y'), $task['date_due']);
|
||||
}
|
||||
else {
|
||||
$task['date_due'] = '';
|
||||
}
|
||||
|
||||
$task['score'] = $task['score'] ?: '';
|
||||
|
||||
$params = array(
|
||||
'values' => $task,
|
||||
'errors' => array(),
|
||||
'task' => $task,
|
||||
'columns_list' => $this->board->getColumnsList($task['project_id']),
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'categories_list' => $this->category->getList($task['project_id']),
|
||||
'ajax' => $this->request->isAjax(),
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a task')
|
||||
);
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->html($this->template->load('task_edit', $params));
|
||||
}
|
||||
else {
|
||||
$this->response->html($this->template->layout('task_edit', $params));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and update a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->task->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->task->update($values)) {
|
||||
$this->session->flash(t('Task updated successfully.'));
|
||||
|
||||
if ($this->request->getIntegerParam('ajax')) {
|
||||
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
|
||||
}
|
||||
else {
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$values['id']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your task.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('task_edit', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'task' => $task,
|
||||
'columns_list' => $this->board->getColumnsList($values['project_id']),
|
||||
'users_list' => $this->project->getUsersList($values['project_id']),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'categories_list' => $this->category->getList($values['project_id']),
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($this->task->close($task['id'])) {
|
||||
$this->session->flash(t('Task closed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to close this task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before to close a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirmClose()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('task_close', array(
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Close a task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function open()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($this->task->open($task['id'])) {
|
||||
$this->session->flash(t('Task opened successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to open this task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before to open a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirmOpen()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('task_open', array(
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Open a task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($this->task->remove($task['id'])) {
|
||||
$this->session->flash(t('Task removed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to remove this task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing a task
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirmRemove()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('task_remove', array(
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Remove a task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate a task (fill the form for a new task)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function duplicate()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
if (! empty($task['date_due'])) {
|
||||
$task['date_due'] = date(t('m/d/Y'), $task['date_due']);
|
||||
}
|
||||
else {
|
||||
$task['date_due'] = '';
|
||||
}
|
||||
|
||||
$task['score'] = $task['score'] ?: '';
|
||||
|
||||
$this->response->html($this->template->layout('task_new', array(
|
||||
'errors' => array(),
|
||||
'values' => $task,
|
||||
'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE),
|
||||
'columns_list' => $this->board->getColumnsList($task['project_id']),
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
'categories_list' => $this->category->getList($task['project_id']),
|
||||
'duplicate' => true,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('New task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit description form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function editDescription()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$params = array(
|
||||
'values' => $task,
|
||||
'errors' => array(),
|
||||
'task' => $task,
|
||||
'ajax' => $this->request->isAjax(),
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit the description')
|
||||
);
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->html($this->template->load('task_edit_description', $params));
|
||||
}
|
||||
else {
|
||||
$this->response->html($this->taskLayout('task_edit_description', $params));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save and validation the description
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function saveDescription()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->task->validateDescriptionCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->task->update($values)) {
|
||||
$this->session->flash(t('Task updated successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your task.'));
|
||||
}
|
||||
|
||||
if ($this->request->getIntegerParam('ajax')) {
|
||||
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
|
||||
}
|
||||
else {
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->taskLayout('task_edit_description', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit the description')
|
||||
)));
|
||||
}
|
||||
}
|
366
sources/app/Controller/User.php
Normal file
366
sources/app/Controller/User.php
Normal file
|
@ -0,0 +1,366 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
/**
|
||||
* User controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class User extends Base
|
||||
{
|
||||
/**
|
||||
* Logout and destroy session
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$this->rememberMe->destroy($this->acl->getUserId());
|
||||
$this->session->close();
|
||||
$this->response->redirect('?controller=user&action=login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form login
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
if (isset($_SESSION['user'])) {
|
||||
$this->response->redirect('?controller=app');
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('user_login', array(
|
||||
'errors' => array(),
|
||||
'values' => array(),
|
||||
'no_layout' => true,
|
||||
'title' => t('Login')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check credentials
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->user->validateLogin($values);
|
||||
|
||||
if ($valid) {
|
||||
$this->response->redirect('?controller=app');
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('user_login', array(
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'no_layout' => true,
|
||||
'title' => t('Login')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* List all users
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$users = $this->user->getAll();
|
||||
$nb_users = count($users);
|
||||
|
||||
$this->response->html(
|
||||
$this->template->layout('user_index', array(
|
||||
'projects' => $this->project->getList(),
|
||||
'users' => $users,
|
||||
'nb_users' => $nb_users,
|
||||
'menu' => 'users',
|
||||
'title' => t('Users').' ('.$nb_users.')'
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to create a new user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->response->html($this->template->layout('user_new', array(
|
||||
'projects' => $this->project->getList(),
|
||||
'errors' => array(),
|
||||
'values' => array(),
|
||||
'menu' => 'users',
|
||||
'title' => t('New user')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save a new user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->user->validateCreation($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->user->create($values)) {
|
||||
$this->session->flash(t('User created successfully.'));
|
||||
$this->response->redirect('?controller=user');
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your user.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('user_new', array(
|
||||
'projects' => $this->project->getList(),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'menu' => 'users',
|
||||
'title' => t('New user')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form to edit a user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$user = $this->user->getById($this->request->getIntegerParam('user_id'));
|
||||
|
||||
if (! $user) $this->notfound();
|
||||
|
||||
if ($this->acl->isRegularUser() && $this->acl->getUserId() != $user['id']) {
|
||||
$this->forbidden();
|
||||
}
|
||||
|
||||
unset($user['password']);
|
||||
|
||||
$this->response->html($this->template->layout('user_edit', array(
|
||||
'projects' => $this->project->filterListByAccess($this->project->getList(), $user['id']),
|
||||
'errors' => array(),
|
||||
'values' => $user,
|
||||
'menu' => 'users',
|
||||
'title' => t('Edit user')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and update a user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($this->acl->isAdminUser()) {
|
||||
$values += array('is_admin' => 0);
|
||||
}
|
||||
else {
|
||||
|
||||
if ($this->acl->getUserId() != $values['id']) {
|
||||
$this->forbidden();
|
||||
}
|
||||
|
||||
if (isset($values['is_admin'])) {
|
||||
unset($values['is_admin']); // Regular users can't be admin
|
||||
}
|
||||
}
|
||||
|
||||
list($valid, $errors) = $this->user->validateModification($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->user->update($values)) {
|
||||
$this->session->flash(t('User updated successfully.'));
|
||||
$this->response->redirect('?controller=user');
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your user.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('user_edit', array(
|
||||
'projects' => $this->project->filterListByAccess($this->project->getList(), $values['id']),
|
||||
'errors' => $errors,
|
||||
'values' => $values,
|
||||
'menu' => 'users',
|
||||
'title' => t('Edit user')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before to remove a user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$user = $this->user->getById($this->request->getIntegerParam('user_id'));
|
||||
|
||||
if (! $user) $this->notfound();
|
||||
|
||||
$this->response->html($this->template->layout('user_remove', array(
|
||||
'user' => $user,
|
||||
'menu' => 'users',
|
||||
'title' => t('Remove user')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
|
||||
if ($user_id && $this->user->remove($user_id)) {
|
||||
$this->session->flash(t('User removed successfully.'));
|
||||
} else {
|
||||
$this->session->flashError(t('Unable to remove this user.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Google authentication
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function google()
|
||||
{
|
||||
$code = $this->request->getStringParam('code');
|
||||
|
||||
if ($code) {
|
||||
|
||||
$profile = $this->google->getGoogleProfile($code);
|
||||
|
||||
if (is_array($profile)) {
|
||||
|
||||
// If the user is already logged, link the account otherwise authenticate
|
||||
if ($this->acl->isLogged()) {
|
||||
|
||||
if ($this->google->updateUser($this->acl->getUserId(), $profile)) {
|
||||
$this->session->flash(t('Your Google Account is linked to your profile successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to link your Google Account.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=user');
|
||||
}
|
||||
else if ($this->google->authenticate($profile['id'])) {
|
||||
$this->response->redirect('?controller=app');
|
||||
}
|
||||
else {
|
||||
$this->response->html($this->template->layout('user_login', array(
|
||||
'errors' => array('login' => t('Google authentication failed')),
|
||||
'values' => array(),
|
||||
'no_layout' => true,
|
||||
'title' => t('Login')
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->google->getAuthorizationUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a Google account
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function unlinkGoogle()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
if ($this->google->unlink($this->acl->getUserId())) {
|
||||
$this->session->flash(t('Your Google Account is not linked anymore to your profile.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to unlink your Google Account.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=user');
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub authentication
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function gitHub()
|
||||
{
|
||||
$code = $this->request->getStringParam('code');
|
||||
|
||||
if ($code) {
|
||||
$profile = $this->gitHub->getGitHubProfile($code);
|
||||
|
||||
if (is_array($profile)) {
|
||||
|
||||
// If the user is already logged, link the account otherwise authenticate
|
||||
if ($this->acl->isLogged()) {
|
||||
|
||||
if ($this->gitHub->updateUser($this->acl->getUserId(), $profile)) {
|
||||
$this->session->flash(t('Your GitHub account was successfully linked to your profile.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to link your GitHub Account.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=user');
|
||||
}
|
||||
else if ($this->gitHub->authenticate($profile['id'])) {
|
||||
$this->response->redirect('?controller=app');
|
||||
}
|
||||
else {
|
||||
$this->response->html($this->template->layout('user_login', array(
|
||||
'errors' => array('login' => t('GitHub authentication failed')),
|
||||
'values' => array(),
|
||||
'no_layout' => true,
|
||||
'title' => t('Login')
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->redirect($this->gitHub->getAuthorizationUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a GitHub account
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function unlinkGitHub()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$this->gitHub->revokeGitHubAccess();
|
||||
|
||||
if ($this->gitHub->unlink($this->acl->getUserId())) {
|
||||
$this->session->flash(t('Your GitHub account is no longer linked to your profile.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to unlink your GitHub Account.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=user');
|
||||
}
|
||||
}
|
161
sources/app/Core/Event.php
Normal file
161
sources/app/Core/Event.php
Normal file
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Event dispatcher class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Event
|
||||
{
|
||||
/**
|
||||
* Contains all listeners
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $listeners = array();
|
||||
|
||||
/**
|
||||
* The last listener executed
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $lastListener = '';
|
||||
|
||||
/**
|
||||
* The last triggered event
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $lastEvent = '';
|
||||
|
||||
/**
|
||||
* Triggered events list
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $events = array();
|
||||
|
||||
/**
|
||||
* Attach a listener object to an event
|
||||
*
|
||||
* @access public
|
||||
* @param string $eventName Event name
|
||||
* @param Listener $listener Object that implements the Listener interface
|
||||
*/
|
||||
public function attach($eventName, Listener $listener)
|
||||
{
|
||||
if (! isset($this->listeners[$eventName])) {
|
||||
$this->listeners[$eventName] = array();
|
||||
}
|
||||
|
||||
$this->listeners[$eventName][] = $listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event
|
||||
*
|
||||
* @access public
|
||||
* @param string $eventName Event name
|
||||
* @param array $data Event data
|
||||
*/
|
||||
public function trigger($eventName, array $data)
|
||||
{
|
||||
if (! $this->isEventTriggered($eventName)) {
|
||||
|
||||
$this->lastEvent = $eventName;
|
||||
$this->events[] = $eventName;
|
||||
|
||||
if (isset($this->listeners[$eventName])) {
|
||||
foreach ($this->listeners[$eventName] as $listener) {
|
||||
if ($listener->execute($data)) {
|
||||
$this->lastListener = get_class($listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last listener executed
|
||||
*
|
||||
* @access public
|
||||
* @return string Event name
|
||||
*/
|
||||
public function getLastListenerExecuted()
|
||||
{
|
||||
return $this->lastListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last fired event
|
||||
*
|
||||
* @access public
|
||||
* @return string Event name
|
||||
*/
|
||||
public function getLastTriggeredEvent()
|
||||
{
|
||||
return $this->lastEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of triggered events
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getTriggeredEvents()
|
||||
{
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an event have been triggered
|
||||
*
|
||||
* @access public
|
||||
* @param string $eventName Event name
|
||||
* @return bool
|
||||
*/
|
||||
public function isEventTriggered($eventName)
|
||||
{
|
||||
return in_array($eventName, $this->events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the list of triggered events
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function clearTriggeredEvents()
|
||||
{
|
||||
$this->events = array();
|
||||
$this->lastEvent = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a listener bind to an event
|
||||
*
|
||||
* @access public
|
||||
* @param string $eventName Event name
|
||||
* @param mixed $instance Instance name or object itself
|
||||
* @return bool Yes or no
|
||||
*/
|
||||
public function hasListener($eventName, $instance)
|
||||
{
|
||||
if (isset($this->listeners[$eventName])) {
|
||||
foreach ($this->listeners[$eventName] as $listener) {
|
||||
if ($listener instanceof $instance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
21
sources/app/Core/Listener.php
Normal file
21
sources/app/Core/Listener.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Event listener interface
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface Listener {
|
||||
|
||||
/**
|
||||
* Execute the listener
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data
|
||||
* @return boolean
|
||||
*/
|
||||
public function execute(array $data);
|
||||
}
|
37
sources/app/Core/Loader.php
Normal file
37
sources/app/Core/Loader.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Loader class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Loader
|
||||
{
|
||||
/**
|
||||
* Load the missing class
|
||||
*
|
||||
* @access public
|
||||
* @param string $class Class name
|
||||
*/
|
||||
public function load($class)
|
||||
{
|
||||
$filename = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
|
||||
|
||||
if (file_exists($filename)) {
|
||||
require $filename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the autoloader
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
spl_autoload_register(array($this, 'load'));
|
||||
}
|
||||
}
|
82
sources/app/Core/Registry.php
Normal file
82
sources/app/Core/Registry.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* The registry class is a dependency injection container
|
||||
*
|
||||
* @property mixed db
|
||||
* @property mixed event
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Registry
|
||||
{
|
||||
/**
|
||||
* Contains all dependencies
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $container = array();
|
||||
|
||||
/**
|
||||
* Contains all instances
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $instances = array();
|
||||
|
||||
/**
|
||||
* Set a dependency
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Unique identifier for the service/parameter
|
||||
* @param mixed $value The value of the parameter or a closure to define an object
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->container[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dependency
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Unique identifier for the service/parameter
|
||||
* @return mixed The value of the parameter or an object
|
||||
* @throws RuntimeException If the identifier is not found
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if (isset($this->container[$name])) {
|
||||
|
||||
if (is_callable($this->container[$name])) {
|
||||
return $this->container[$name]();
|
||||
}
|
||||
else {
|
||||
return $this->container[$name];
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Identifier not found in the registry: '.$name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a shared instance of a dependency
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Unique identifier for the service/parameter
|
||||
* @return mixed Same object instance of the dependency
|
||||
*/
|
||||
public function shared($name)
|
||||
{
|
||||
if (! isset($this->instances[$name])) {
|
||||
$this->instances[$name] = $this->$name;
|
||||
}
|
||||
|
||||
return $this->instances[$name];
|
||||
}
|
||||
}
|
139
sources/app/Core/Request.php
Normal file
139
sources/app/Core/Request.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Request class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* Get URL string parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Parameter name
|
||||
* @param string $default_value Default value
|
||||
* @return string
|
||||
*/
|
||||
public function getStringParam($name, $default_value = '')
|
||||
{
|
||||
return isset($_GET[$name]) ? $_GET[$name] : $default_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL integer parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Parameter name
|
||||
* @param integer $default_value Default value
|
||||
* @return integer
|
||||
*/
|
||||
public function getIntegerParam($name, $default_value = 0)
|
||||
{
|
||||
return isset($_GET[$name]) && ctype_digit($_GET[$name]) ? (int) $_GET[$name] : $default_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a form value
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Form field name
|
||||
* @return string|null
|
||||
*/
|
||||
public function getValue($name)
|
||||
{
|
||||
$values = $this->getValues();
|
||||
return isset($values[$name]) ? $values[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form values or unserialized json request
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
if (! empty($_POST)) {
|
||||
|
||||
if (Security::validateCSRFFormToken($_POST)) {
|
||||
return $_POST;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = json_decode($this->getBody(), true);
|
||||
|
||||
if ($result) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw body of the HTTP request
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
return file_get_contents('php://input');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of an uploaded file
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Form file name
|
||||
* @return string
|
||||
*/
|
||||
public function getFileContent($name)
|
||||
{
|
||||
if (isset($_FILES[$name])) {
|
||||
return file_get_contents($_FILES[$name]['tmp_name']);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the HTTP request is sent with the POST method
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isPost()
|
||||
{
|
||||
return isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the HTTP request is an Ajax request
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isAjax()
|
||||
{
|
||||
return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a HTTP header value
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Header name
|
||||
* @return string
|
||||
*/
|
||||
public function getHeader($name)
|
||||
{
|
||||
$name = 'HTTP_'.str_replace('-', '_', strtoupper($name));
|
||||
return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
|
||||
}
|
||||
}
|
254
sources/app/Core/Response.php
Normal file
254
sources/app/Core/Response.php
Normal file
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Response class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Send no cache headers
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function nocache()
|
||||
{
|
||||
header('Pragma: no-cache');
|
||||
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
||||
|
||||
// Use no-store due to a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=28035
|
||||
header('Cache-Control: no-store, must-revalidate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a custom Content-Type header
|
||||
*
|
||||
* @access public
|
||||
* @param string $mimetype Mime-type
|
||||
*/
|
||||
public function contentType($mimetype)
|
||||
{
|
||||
header('Content-Type: '.$mimetype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the browser to download an attachment
|
||||
*
|
||||
* @access public
|
||||
* @param string $filename File name
|
||||
*/
|
||||
public function forceDownload($filename)
|
||||
{
|
||||
header('Content-Disposition: attachment; filename="'.$filename.'"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a custom HTTP status code
|
||||
*
|
||||
* @access public
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function status($status_code)
|
||||
{
|
||||
header('Status: '.$status_code);
|
||||
header($_SERVER['SERVER_PROTOCOL'].' '.$status_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to another URL
|
||||
*
|
||||
* @access public
|
||||
* @param string $url Redirection URL
|
||||
*/
|
||||
public function redirect($url)
|
||||
{
|
||||
header('Location: '.$url);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Json response
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Data to serialize in json
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function json(array $data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
$this->nocache();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a text response
|
||||
*
|
||||
* @access public
|
||||
* @param string $data Raw data
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function text($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
$this->nocache();
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $data;
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a HTML response
|
||||
*
|
||||
* @access public
|
||||
* @param string $data Raw data
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function html($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
$this->nocache();
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo $data;
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a XML response
|
||||
*
|
||||
* @access public
|
||||
* @param string $data Raw data
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function xml($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
$this->nocache();
|
||||
header('Content-Type: text/xml; charset=utf-8');
|
||||
echo $data;
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a javascript response
|
||||
*
|
||||
* @access public
|
||||
* @param string $data Raw data
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function js($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
|
||||
header('Content-Type: text/javascript; charset=utf-8');
|
||||
echo $data;
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a binary response
|
||||
*
|
||||
* @access public
|
||||
* @param string $data Raw data
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function binary($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
$this->nocache();
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Content-Type: application/octet-stream');
|
||||
echo $data;
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the security header: Content-Security-Policy
|
||||
*
|
||||
* @access public
|
||||
* @param array $policies CSP rules
|
||||
*/
|
||||
public function csp(array $policies = array())
|
||||
{
|
||||
$policies['default-src'] = "'self'";
|
||||
$values = '';
|
||||
|
||||
foreach ($policies as $policy => $hosts) {
|
||||
|
||||
if (is_array($hosts)) {
|
||||
|
||||
$acl = '';
|
||||
|
||||
foreach ($hosts as &$host) {
|
||||
|
||||
if ($host === '*' || $host === 'self' || strpos($host, 'http') === 0) {
|
||||
$acl .= $host.' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
$acl = $hosts;
|
||||
}
|
||||
|
||||
$values .= $policy.' '.trim($acl).'; ';
|
||||
}
|
||||
|
||||
header('Content-Security-Policy: '.$values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the security header: X-Content-Type-Options
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function nosniff()
|
||||
{
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the security header: X-XSS-Protection
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function xss()
|
||||
{
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the security header: Strict-Transport-Security (only if we use HTTPS)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function hsts()
|
||||
{
|
||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
|
||||
header('Strict-Transport-Security: max-age=31536000');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the security header: X-Frame-Options (deny by default)
|
||||
*
|
||||
* @access public
|
||||
* @param string $mode Frame option mode
|
||||
* @param array $urls Allowed urls for the given mode
|
||||
*/
|
||||
public function xframe($mode = 'DENY', array $urls = array())
|
||||
{
|
||||
header('X-Frame-Options: '.$mode.' '.implode(' ', $urls));
|
||||
}
|
||||
}
|
113
sources/app/Core/Router.php
Normal file
113
sources/app/Core/Router.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Router class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* Controller name
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $controller = '';
|
||||
|
||||
/**
|
||||
* Action name
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $action = '';
|
||||
|
||||
/**
|
||||
* Registry instance
|
||||
*
|
||||
* @access private
|
||||
* @var \Core\Registry
|
||||
*/
|
||||
private $registry;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param Registry $registry Registry instance
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
*/
|
||||
public function __construct(Registry $registry, $controller = '', $action = '')
|
||||
{
|
||||
$this->registry = $registry;
|
||||
$this->controller = empty($_GET['controller']) ? $controller : $_GET['controller'];
|
||||
$this->action = empty($_GET['action']) ? $action : $_GET['action'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check controller and action parameter
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Controller or action name
|
||||
* @param string $default_value Default value if validation fail
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize($value, $default_value)
|
||||
{
|
||||
return ! ctype_alpha($value) || empty($value) ? $default_value : strtolower($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a controller and execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param string $filename Controller filename
|
||||
* @param string $class Class name
|
||||
* @param string $method Method name
|
||||
* @return bool
|
||||
*/
|
||||
public function load($filename, $class, $method)
|
||||
{
|
||||
if (file_exists($filename)) {
|
||||
|
||||
require $filename;
|
||||
|
||||
if (! method_exists($class, $method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$instance = new $class($this->registry);
|
||||
$instance->request = new Request;
|
||||
$instance->response = new Response;
|
||||
$instance->session = new Session;
|
||||
$instance->template = new Template;
|
||||
$instance->beforeAction($this->controller, $this->action);
|
||||
$instance->$method();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a route
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$this->controller = $this->sanitize($this->controller, 'app');
|
||||
$this->action = $this->sanitize($this->action, 'index');
|
||||
$filename = __DIR__.'/../Controller/'.ucfirst($this->controller).'.php';
|
||||
|
||||
if (! $this->load($filename, '\Controller\\'.$this->controller, $this->action)) {
|
||||
die('Page not found!');
|
||||
}
|
||||
}
|
||||
}
|
87
sources/app/Core/Security.php
Normal file
87
sources/app/Core/Security.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Security class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Security
|
||||
{
|
||||
/**
|
||||
* Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid()
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return string Random token
|
||||
*/
|
||||
public static function generateToken()
|
||||
{
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return bin2hex(\openssl_random_pseudo_bytes(30));
|
||||
}
|
||||
else if (ini_get('open_basedir') === '' && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
||||
return hash('sha256', file_get_contents('/dev/urandom', false, null, 0, 30));
|
||||
}
|
||||
|
||||
return hash('sha256', uniqid(mt_rand(), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and store a CSRF token in the current session
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return string Random token
|
||||
*/
|
||||
public static function getCSRFToken()
|
||||
{
|
||||
$nonce = self::generateToken();
|
||||
|
||||
if (empty($_SESSION['csrf_tokens'])) {
|
||||
$_SESSION['csrf_tokens'] = array();
|
||||
}
|
||||
|
||||
$_SESSION['csrf_tokens'][$nonce] = true;
|
||||
|
||||
return $nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token exists for the current session (a token can be used only one time)
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $token CSRF token
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateCSRFToken($token)
|
||||
{
|
||||
if (isset($_SESSION['csrf_tokens'][$token])) {
|
||||
unset($_SESSION['csrf_tokens'][$token]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token used in a form is correct and then remove the value
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateCSRFFormToken(array &$values)
|
||||
{
|
||||
if (! empty($values['csrf_token']) && self::validateCSRFToken($values['csrf_token'])) {
|
||||
unset($values['csrf_token']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
118
sources/app/Core/Session.php
Normal file
118
sources/app/Core/Session.php
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Session class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
/**
|
||||
* Sesion lifetime
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const SESSION_LIFETIME = 7200; // 2 hours
|
||||
|
||||
/**
|
||||
* Open a session
|
||||
*
|
||||
* @access public
|
||||
* @param string $base_path Cookie path
|
||||
* @param string $save_path Custom session save path
|
||||
*/
|
||||
public function open($base_path = '/', $save_path = '')
|
||||
{
|
||||
if ($save_path !== '') {
|
||||
session_save_path($save_path);
|
||||
}
|
||||
|
||||
// HttpOnly and secure flags for session cookie
|
||||
session_set_cookie_params(
|
||||
self::SESSION_LIFETIME,
|
||||
$base_path ?: '/',
|
||||
null,
|
||||
! empty($_SERVER['HTTPS']),
|
||||
true
|
||||
);
|
||||
|
||||
// Avoid session id in the URL
|
||||
ini_set('session.use_only_cookies', '1');
|
||||
|
||||
// Ensure session ID integrity
|
||||
ini_set('session.entropy_file', '/dev/urandom');
|
||||
ini_set('session.entropy_length', '32');
|
||||
ini_set('session.hash_bits_per_character', 6);
|
||||
|
||||
// If session was autostarted with session.auto_start = 1 in php.ini destroy it, otherwise we cannot login
|
||||
if (isset($_SESSION))
|
||||
{
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
// Custom session name
|
||||
session_name('__S');
|
||||
|
||||
session_start();
|
||||
|
||||
// Regenerate the session id to avoid session fixation issue
|
||||
if (empty($_SESSION['__validated'])) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['__validated'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the session
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
// Flush all sessions variables
|
||||
$_SESSION = array();
|
||||
|
||||
// Destroy the session cookie
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
time() - 42000,
|
||||
$params['path'],
|
||||
$params['domain'],
|
||||
$params['secure'],
|
||||
$params['httponly']
|
||||
);
|
||||
}
|
||||
|
||||
// Destroy session data
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a flash message (success notification)
|
||||
*
|
||||
* @access public
|
||||
* @param string $message Message
|
||||
*/
|
||||
public function flash($message)
|
||||
{
|
||||
$_SESSION['flash_message'] = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a flash error message (error notification)
|
||||
*
|
||||
* @access public
|
||||
* @param string $message Message
|
||||
*/
|
||||
public function flashError($message)
|
||||
{
|
||||
$_SESSION['flash_error_message'] = $message;
|
||||
}
|
||||
}
|
72
sources/app/Core/Template.php
Normal file
72
sources/app/Core/Template.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Template class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Template
|
||||
{
|
||||
/**
|
||||
* Template path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PATH = 'app/Templates/';
|
||||
|
||||
/**
|
||||
* Load a template
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $template->load('template_name', ['bla' => 'value']);
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
if (func_num_args() < 1 || func_num_args() > 2) {
|
||||
die('Invalid template arguments');
|
||||
}
|
||||
|
||||
if (! file_exists(self::PATH.func_get_arg(0).'.php')) {
|
||||
die('Unable to load the template: "'.func_get_arg(0).'"');
|
||||
}
|
||||
|
||||
if (func_num_args() === 2) {
|
||||
|
||||
if (! is_array(func_get_arg(1))) {
|
||||
die('Template variables must be an array');
|
||||
}
|
||||
|
||||
extract(func_get_arg(1));
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
include self::PATH.func_get_arg(0).'.php';
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a page layout
|
||||
*
|
||||
* @access public
|
||||
* @param string $template_name Template name
|
||||
* @param array $template_args Key/value map
|
||||
* @param string $layout_name Layout name
|
||||
* @return string
|
||||
*/
|
||||
public function layout($template_name, array $template_args = array(), $layout_name = 'layout')
|
||||
{
|
||||
return $this->load(
|
||||
$layout_name,
|
||||
$template_args + array('content_for_layout' => $this->load($template_name, $template_args))
|
||||
);
|
||||
}
|
||||
}
|
164
sources/app/Core/Translator.php
Normal file
164
sources/app/Core/Translator.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Translator class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Translator
|
||||
{
|
||||
/**
|
||||
* Locales path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PATH = 'app/Locales/';
|
||||
|
||||
/**
|
||||
* Locales
|
||||
*
|
||||
* @static
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private static $locales = array();
|
||||
|
||||
/**
|
||||
* Get a translation
|
||||
*
|
||||
* $translator->translate('I have %d kids', 5);
|
||||
*
|
||||
* @access public
|
||||
* @param $identifier
|
||||
* @return string
|
||||
*/
|
||||
public function translate($identifier)
|
||||
{
|
||||
$args = func_get_args();
|
||||
|
||||
array_shift($args);
|
||||
array_unshift($args, $this->get($identifier, $identifier));
|
||||
|
||||
foreach ($args as &$arg) {
|
||||
$arg = htmlspecialchars($arg, ENT_QUOTES, 'UTF-8', false);
|
||||
}
|
||||
|
||||
return call_user_func_array(
|
||||
'sprintf',
|
||||
$args
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted number
|
||||
*
|
||||
* $translator->number(1234.56);
|
||||
*
|
||||
* @access public
|
||||
* @param float $number Number to format
|
||||
* @return string
|
||||
*/
|
||||
public function number($number)
|
||||
{
|
||||
return number_format(
|
||||
$number,
|
||||
$this->get('number.decimals', 2),
|
||||
$this->get('number.decimals_separator', '.'),
|
||||
$this->get('number.thousands_separator', ',')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted currency number
|
||||
*
|
||||
* $translator->currency(1234.56);
|
||||
*
|
||||
* @access public
|
||||
* @param float $amount Number to format
|
||||
* @return string
|
||||
*/
|
||||
public function currency($amount)
|
||||
{
|
||||
$position = $this->get('currency.position', 'before');
|
||||
$symbol = $this->get('currency.symbol', '$');
|
||||
$str = '';
|
||||
|
||||
if ($position === 'before') {
|
||||
$str .= $symbol;
|
||||
}
|
||||
|
||||
$str .= $this->number($amount);
|
||||
|
||||
if ($position === 'after') {
|
||||
$str .= ' '.$symbol;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted datetime
|
||||
*
|
||||
* $translator->datetime('%Y-%m-%d', time());
|
||||
*
|
||||
* @access public
|
||||
* @param string $format Format defined by the strftime function
|
||||
* @param integer $timestamp Unix timestamp
|
||||
* @return string
|
||||
*/
|
||||
public function datetime($format, $timestamp)
|
||||
{
|
||||
if (! $timestamp) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$format = $this->get($format, $format);
|
||||
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||
$format = str_replace('%e', '%d', $format);
|
||||
$format = str_replace('%G', '%Y', $format);
|
||||
$format = str_replace('%k', '%H', $format);
|
||||
}
|
||||
|
||||
return strftime($format, (int) $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an identifier from the translations or return the default
|
||||
*
|
||||
* @access public
|
||||
* @param string $identifier Locale identifier
|
||||
* @param string $default Default value
|
||||
* @return string
|
||||
*/
|
||||
public function get($identifier, $default = '')
|
||||
{
|
||||
if (isset(self::$locales[$identifier])) {
|
||||
return self::$locales[$identifier];
|
||||
}
|
||||
else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load translations
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $language Locale code: fr_FR
|
||||
*/
|
||||
public static function load($language)
|
||||
{
|
||||
setlocale(LC_TIME, $language.'.UTF-8', $language);
|
||||
|
||||
$filename = self::PATH.$language.DIRECTORY_SEPARATOR.'translations.php';
|
||||
|
||||
if (file_exists($filename)) {
|
||||
self::$locales = require $filename;
|
||||
}
|
||||
}
|
||||
}
|
51
sources/app/Event/TaskModification.php
Normal file
51
sources/app/Event/TaskModification.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Event;
|
||||
|
||||
use Core\Listener;
|
||||
use Model\Project;
|
||||
|
||||
/**
|
||||
* Task modification listener
|
||||
*
|
||||
* @package events
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskModification implements Listener
|
||||
{
|
||||
/**
|
||||
* Project model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Project
|
||||
*/
|
||||
private $project;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Model\Project $project Project model instance
|
||||
*/
|
||||
public function __construct(Project $project)
|
||||
{
|
||||
$this->project = $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Event data dictionary
|
||||
* @return bool True if the action was executed or false when not executed
|
||||
*/
|
||||
public function execute(array $data)
|
||||
{
|
||||
if (isset($data['project_id'])) {
|
||||
$this->project->updateModificationDate($data['project_id']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
390
sources/app/Locales/de_DE/translations.php
Normal file
390
sources/app/Locales/de_DE/translations.php
Normal file
|
@ -0,0 +1,390 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
'English' => 'Englisch',
|
||||
'German' => 'Deutsch',
|
||||
'French' => 'Französisch',
|
||||
'Polish' => 'Polnisch',
|
||||
'Portuguese (Brazilian)' => 'Portugiesisch (Brasilien)',
|
||||
'Spanish' => 'Spanisch',
|
||||
'Chinese (Simplified)' => 'Chinesisch (vereinfacht)',
|
||||
'None' => 'Kein',
|
||||
'edit' => 'Bearbeiten',
|
||||
'Edit' => 'Bearbeiten',
|
||||
'remove' => 'Entfernen',
|
||||
'Remove' => 'Entfernen',
|
||||
'Update' => 'Aktualisieren',
|
||||
'Yes' => 'Ja',
|
||||
'No' => 'Nein',
|
||||
'cancel' => 'Abbrechen',
|
||||
'or' => 'oder',
|
||||
'Yellow' => 'Gelb',
|
||||
'Blue' => 'Blau',
|
||||
'Green' => 'Grün',
|
||||
'Purple' => 'Violett',
|
||||
'Red' => 'Rot',
|
||||
'Orange' => 'Orange',
|
||||
'Grey' => 'Grau',
|
||||
'Save' => 'Speichern',
|
||||
'Login' => 'Anmelden',
|
||||
'Official website:' => 'Offizielle Webseite:',
|
||||
'Unassigned' => 'Nicht zugeordnet',
|
||||
'View this task' => 'Aufgabe ansehen',
|
||||
'Remove user' => 'Benutzer löschen',
|
||||
'Do you really want to remove this user: "%s"?' => 'Soll dieser Benutzer wirklich gelöscht werden: «%s»?',
|
||||
'New user' => 'Neuer Benutzer',
|
||||
'All users' => 'Alle Benutzer',
|
||||
'Username' => 'Benutzername',
|
||||
'Password' => 'Passwort',
|
||||
'Default Project' => 'Standardprojekt',
|
||||
'Administrator' => 'Administrator',
|
||||
'Sign in' => 'Anmelden',
|
||||
'Users' => 'Benutzer',
|
||||
'No user' => 'Kein Benutzer',
|
||||
'Forbidden' => 'Verboten',
|
||||
'Access Forbidden' => 'Zugang verboten',
|
||||
'Only administrators can access to this page.' => 'Nur Administratoren haben Zugang zu dieser Seite.',
|
||||
'Edit user' => 'Benutzer bearbeiten',
|
||||
'Logout' => 'Abmelden',
|
||||
'Bad username or password' => 'Falscher Benutzername oder Passwort',
|
||||
'users' => 'Benutzer',
|
||||
'projects' => 'Projekte',
|
||||
'Edit project' => 'Projekt bearbeiten',
|
||||
'Name' => 'Name',
|
||||
'Activated' => 'Aktiviert',
|
||||
'Projects' => 'Projekte',
|
||||
'No project' => 'Keine Projekte',
|
||||
'Project' => 'Projekt',
|
||||
'Status' => 'Status',
|
||||
'Tasks' => 'Aufgabe',
|
||||
'Board' => 'Pinwand',
|
||||
'Actions' => 'Aktionen',
|
||||
'Inactive' => 'Inaktiv',
|
||||
'Active' => 'Aktiv',
|
||||
'Column %d' => 'Spalte %d',
|
||||
'Add this column' => 'Diese Spalte hinzufügen',
|
||||
'%d tasks on the board' => '%d Aufgaben auf dieser Pinwand',
|
||||
'%d tasks in total' => '%d Aufgaben gesamt',
|
||||
'Unable to update this board.' => 'Ändern dieser Pinwand nicht möglich.',
|
||||
'Edit board' => 'Pinwand bearbeiten',
|
||||
'Disable' => 'Deaktivieren',
|
||||
'Enable' => 'Aktivieren',
|
||||
'New project' => 'Neues Projekt',
|
||||
'Do you really want to remove this project: "%s"?' => 'Soll dieses Projekt wirklich gelöscht werden: «%s»?',
|
||||
'Remove project' => 'Projekt löschen',
|
||||
'Boards' => 'Pinwände',
|
||||
'Edit the board for "%s"' => 'Pinwand für «%s» bearbeiten',
|
||||
'All projects' => 'Alle Projekte',
|
||||
'Change columns' => 'Spalten ändern',
|
||||
'Add a new column' => 'Neue Spalte hinzufügen',
|
||||
'Title' => 'Titel',
|
||||
'Add Column' => 'Neue Spalte',
|
||||
'Project "%s"' => 'Projekt «%s»',
|
||||
'Nobody assigned' => 'Nicht zugeordnet',
|
||||
'Assigned to %s' => 'Zuständiger: %s',
|
||||
'Remove a column' => 'Spalte löschen',
|
||||
'Remove a column from a board' => 'Spalte einer Pinwand löschen',
|
||||
'Unable to remove this column.' => 'Löschen dieser Spalte nicht möglich.',
|
||||
'Do you really want to remove this column: "%s"?' => 'Soll diese Spalte wirklich gelöscht werden: «%s»?',
|
||||
'This action will REMOVE ALL TASKS associated to this column!' => 'ALLE AUFGABEN dieser Spalte werden GELÖSCHT!',
|
||||
'Settings' => 'Einstellungen',
|
||||
'Application settings' => 'Anwendungskonfiguration',
|
||||
'Language' => 'Sprache',
|
||||
'Webhooks token:' => 'Webhooks Token:',
|
||||
'API token:' => 'API Token:',
|
||||
'More information' => 'Mehr Informationen',
|
||||
'Database size:' => 'Datenbankgröße:',
|
||||
'Download the database' => 'Download der Datenbank',
|
||||
'Optimize the database' => 'Optimieren der Datenbank',
|
||||
'(VACUUM command)' => '(VACUUM Kommando)',
|
||||
'(Gzip compressed Sqlite file)' => '(Gzip komprimierte Sqlite Datei)',
|
||||
'User settings' => 'Benutzereinstellungen',
|
||||
'My default project:' => 'Standardprojekt:',
|
||||
'Close a task' => 'Aufgabe abschließen',
|
||||
'Do you really want to close this task: "%s"?' => 'Soll diese Aufgabe wirklich abgeschlossen werden: «%s»?',
|
||||
'Edit a task' => 'Aufgabe bearbeiten',
|
||||
'Column' => 'Spalte',
|
||||
'Color' => 'Farbe',
|
||||
'Assignee' => 'Zuständiger',
|
||||
'Create another task' => 'Weitere Aufgabe erstellen',
|
||||
'New task' => 'Neue Aufgabe',
|
||||
'Open a task' => 'Öffne eine Aufgabe',
|
||||
'Do you really want to open this task: "%s"?' => 'Soll diese Aufgabe wirklich wieder geöffnet werden: «%s»?',
|
||||
'Back to the board' => 'Zurück zur Pinwand',
|
||||
'Created on %B %e, %G at %k:%M %p' => 'Erstellt am %d.%m.%Y um %H:%M',
|
||||
'There is nobody assigned' => 'Die Aufgabe wurde niemand zugewiesen',
|
||||
'Column on the board:' => 'Spalte:',
|
||||
'Status is open' => 'Status ist geöffnet',
|
||||
'Status is closed' => 'Status ist abgeschlossen',
|
||||
'Close this task' => 'Aufgabe abschließen',
|
||||
'Open this task' => 'Aufgabe wieder öffnen',
|
||||
'There is no description.' => 'Keine Beschreibung vorhanden.',
|
||||
'Add a new task' => 'Neue Aufgabe hinzufügen',
|
||||
'The username is required' => 'Der Benutzername ist obligatorisch',
|
||||
'The maximum length is %d characters' => 'Die maximale Länge sind %d Zeichen',
|
||||
'The minimum length is %d characters' => 'Die minimale Länge sind %d Zeichen',
|
||||
'The password is required' => 'Das Passwort ist obligatorisch',
|
||||
'This value must be an integer' => 'Dieser Wert muss eine Ganzzahl sein',
|
||||
'The username must be unique' => 'Der Benutzername muss eindeutig sein',
|
||||
'The username must be alphanumeric' => 'Der Benutzername muss alphanumerisch sein',
|
||||
'The user id is required' => 'Die Benutzer ID ist obligatorisch',
|
||||
'Passwords don\'t match' => 'Passwörter nicht gleich',
|
||||
'The confirmation is required' => 'Die Bestätigung ist erforderlich',
|
||||
'The column is required' => 'Die Spalte ist anzugeben',
|
||||
'The project is required' => 'Das Projekt ist anzugeben',
|
||||
'The color is required' => 'Die Farbe ist anzugeben',
|
||||
'The id is required' => 'Die ID ist anzugeben',
|
||||
'The project id is required' => 'Die Projekt ID ist anzugeben',
|
||||
'The project name is required' => 'Der Projektname ist anzugeben',
|
||||
'This project must be unique' => 'Der Projektname muss eindeutig sein',
|
||||
'The title is required' => 'Der Titel ist anzugeben',
|
||||
'The language is required' => 'Die Sprache ist erforderlich',
|
||||
'There is no active project, the first step is to create a new project.' => 'Es gibt kein aktives Projekt. Der erste Schritt ist ein Projekt zu erstellen.',
|
||||
'Settings saved successfully.' => 'Einstellungen erfolgreich gespeichert.',
|
||||
'Unable to save your settings.' => 'Speichern der Einstellungen nicht möglich.',
|
||||
'Database optimization done.' => 'Optimieren der Datenbank abgeschlossen.',
|
||||
'Your project have been created successfully.' => 'Das Projekt wurde erfolgreich erstellt.',
|
||||
'Unable to create your project.' => 'Erstellen des Projekts nicht möglich.',
|
||||
'Project updated successfully.' => 'Projekt erfolgreich geändert.',
|
||||
'Unable to update this project.' => 'Änderung des Projekts nicht möglich.',
|
||||
'Unable to remove this project.' => 'Löschen des Projekts nicht möglich.',
|
||||
'Project removed successfully.' => 'Projekt erfolgreich gelöscht.',
|
||||
'Project activated successfully.' => 'Projekt erfolgreich aktiviert.',
|
||||
'Unable to activate this project.' => 'Aktivieren des Projekts nicht möglich.',
|
||||
'Project disabled successfully.' => 'Projekt erfolgreich deaktiviert.',
|
||||
'Unable to disable this project.' => 'Deaktivieren des Projekts nicht möglich.',
|
||||
'Unable to open this task.' => 'Wieder eröffnen der Aufgabe nicht möglich.',
|
||||
'Task opened successfully.' => 'Aufgabe erfolgreich wieder eröffnet.',
|
||||
'Unable to close this task.' => 'Abschließen der Aufgabe nicht möglich.',
|
||||
'Task closed successfully.' => 'Aufgabe erfolgreich geschlossen.',
|
||||
'Unable to update your task.' => 'Aktualisieren der Aufgabe nicht möglich.',
|
||||
'Task updated successfully.' => 'Aufgabe erfolgreich aktualisiert.',
|
||||
'Unable to create your task.' => 'Erstellen der Aufgabe nicht möglich.',
|
||||
'Task created successfully.' => 'Aufgabe erfolgreich erstellt.',
|
||||
'User created successfully.' => 'Benutzer erfolgreich erstellt.',
|
||||
'Unable to create your user.' => 'Erstellen des Benutzers nicht möglich.',
|
||||
'User updated successfully.' => 'Benutzer erfolgreich geändert.',
|
||||
'Unable to update your user.' => 'Änderung des Benutzers nicht möglich.',
|
||||
'User removed successfully.' => 'Benutzer erfolgreich gelöscht.',
|
||||
'Unable to remove this user.' => 'Löschen des Benutzers nicht möglich.',
|
||||
'Board updated successfully.' => 'Pinwand erfolgreich geändert.',
|
||||
'Ready' => 'Bereit',
|
||||
'Backlog' => 'Ideen',
|
||||
'Work in progress' => 'In Arbeit',
|
||||
'Done' => 'Erledigt',
|
||||
'Application version:' => 'Version:',
|
||||
'Completed on %B %e, %G at %k:%M %p' => 'Abgeschlossen am %d.%m.%Y um %H:%M',
|
||||
'%B %e, %G at %k:%M %p' => '%d.%m.%Y um %H:%M',
|
||||
'Date created' => 'Erstellt am',
|
||||
'Date completed' => 'Abgeschlossen am',
|
||||
'Id' => 'ID',
|
||||
'No task' => 'Keine Aufgabe',
|
||||
'Completed tasks' => 'Abgeschlossene Aufgaben',
|
||||
'List of projects' => 'Liste der Projekte',
|
||||
'Completed tasks for "%s"' => 'Abgeschlossene Aufgaben für «%s»',
|
||||
'%d closed tasks' => '%d abgeschlossene Aufgaben',
|
||||
'no task for this project' => 'Keine Aufgaben in diesem Projekt',
|
||||
'Public link' => 'Öffentlicher Link',
|
||||
'There is no column in your project!' => 'Es gibt keine Spalte in deinem Projekt!',
|
||||
'Change assignee' => 'Zuständigkeit ändern',
|
||||
'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: «%s»',
|
||||
'Timezone' => 'Zeitzone',
|
||||
'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!',
|
||||
'Page not found' => 'Seite nicht gefunden',
|
||||
'Story Points' => 'Aufwand (Story Points)',
|
||||
'limit' => 'Limit',
|
||||
'Task limit' => 'Maximale Anzahl von Aufgaben',
|
||||
'This value must be greater than %d' => 'Dieser Wert muss größer sein als %d',
|
||||
'Edit project access list' => 'Zugriffsberechtigungen des Projektes bearbeiten',
|
||||
'Edit users access' => 'Benutzerzugriff',
|
||||
'Allow this user' => 'Diesen Benutzer authorisieren',
|
||||
'Project access list for "%s"' => 'Zugriffsliste für Projekt «%s»',
|
||||
'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugang zum Projekt:',
|
||||
'Don\'t forget that administrators have access to everything.' => 'Nicht vergessen: Administratoren haben überall Zugang.',
|
||||
'revoke' => 'entfernen',
|
||||
'List of authorized users' => 'Liste der authorisierten Benutzer',
|
||||
'User' => 'Benutzer',
|
||||
'Everybody have access to this project.' => 'Jeder hat Zugang zu diesem Projekt.',
|
||||
'You are not allowed to access to this project.' => 'Unzureichende Zugriffsrechte zu diesem Projekt.',
|
||||
'Comments' => 'Kommentare',
|
||||
'Post comment' => 'Kommentieren',
|
||||
'Write your text in Markdown' => 'Schreibe deinen Text in Markdown-Syntax',
|
||||
'Leave a comment' => 'Kommentar eingeben...',
|
||||
'Comment is required' => 'Ein Kommentar wird benötigt',
|
||||
'Leave a description' => 'Beschreibung eingeben...',
|
||||
'Comment added successfully.' => 'Kommentar erfolgreich hinzugefügt.',
|
||||
'Unable to create your comment.' => 'Hinzufügen eines Kommentars nicht möglich.',
|
||||
'The description is required' => 'Eine Beschreibung wird benötigt',
|
||||
'Edit this task' => 'Aufgabe bearbeiten',
|
||||
'Due Date' => 'Fällig am',
|
||||
'm/d/Y' => 'd.m.Y', // Date format parsed with php
|
||||
'month/day/year' => 'TT.MM.JJJJ', // Help shown to the user
|
||||
'Invalid date' => 'Ungültiges Datum',
|
||||
'Must be done before %B %e, %G' => 'Muss vor dem %d.%m.%Y erledigt werden',
|
||||
'%B %e, %G' => '%d.%m.%Y',
|
||||
'Automatic actions' => 'Automatische Aktionen',
|
||||
'Your automatic action have been created successfully.' => 'Die Automatische Aktion wurde erfolgreich erstellt.',
|
||||
'Unable to create your automatic action.' => 'Automatische Aktion konnte nicht erstellt werden.',
|
||||
'Remove an action' => 'Aktion löschen',
|
||||
'Unable to remove this action.' => 'Aktion konnte nicht gelöscht werden',
|
||||
'Action removed successfully.' => 'Aktion erfolgreich gelöscht.',
|
||||
'Automatic actions for the project "%s"' => 'Automatische Aktionen für das Projekt «%s»',
|
||||
'Defined actions' => 'Definierte Aktionen',
|
||||
'Add an action' => 'Aktion hinzufügen',
|
||||
'Event name' => 'Ereignis',
|
||||
'Action name' => 'Aktion',
|
||||
'Action parameters' => 'Aktionsparameter',
|
||||
'Action' => 'Aktion',
|
||||
'Event' => 'Ereignis',
|
||||
'When the selected event occurs execute the corresponding action.' => 'Wenn das gewählte Ereignis eintritt, führe die zugehörige Aktion aus.',
|
||||
'Next step' => 'Weiter',
|
||||
'Define action parameters' => 'Aktionsparameter definieren',
|
||||
'Save this action' => 'Aktion speichern',
|
||||
'Do you really want to remove this action: "%s"?' => 'Soll diese Aktion wirklich gelöscht werden: «%s»?',
|
||||
'Remove an automatic action' => 'Löschen einer automatischen Aktion',
|
||||
'Close the task' => 'Aufgabe abschließen',
|
||||
'Assign the task to a specific user' => 'Aufgabe einem Benutzer zuordnen',
|
||||
'Assign the task to the person who does the action' => 'Aufgabe dem Benutzer zuordnen, der die Aktion ausgeführt hat',
|
||||
'Duplicate the task to another project' => 'Aufgabe in ein anderes Projekt kopieren',
|
||||
'Move a task to another column' => 'Aufgabe in andere Spalte verschoben',
|
||||
'Move a task to another position in the same column' => 'Aufgabe an andere Position in der gleichen Spalte verschoben',
|
||||
'Task modification' => 'Änderung einer Aufgabe',
|
||||
'Task creation' => 'Erstellung einer Aufgabe',
|
||||
'Open a closed task' => 'Abgeschlossenen Aufgabe wieder eröffnen',
|
||||
'Closing a task' => 'Aufgabe abschließen',
|
||||
'Assign a color to a specific user' => 'Einem Benutzer eine Farbe zuordnen',
|
||||
'Column title' => 'Spaltentitel',
|
||||
'Position' => 'Position',
|
||||
'Move Up' => 'nach oben',
|
||||
'Move Down' => 'nach unten',
|
||||
'Duplicate to another project' => 'In ein anderes Projekt duplizieren',
|
||||
'Duplicate' => 'Duplizieren',
|
||||
'link' => 'Link',
|
||||
'Update this comment' => 'Kommentar aktualisieren',
|
||||
'Comment updated successfully.' => 'Kommentar erfolgreich aktualisiert.',
|
||||
'Unable to update your comment.' => 'Kommentar konnte nicht aktualisiert werden.',
|
||||
'Remove a comment' => 'Kommentar löschen',
|
||||
'Comment removed successfully.' => 'Kommentar erfolgreich gelöscht.',
|
||||
'Unable to remove this comment.' => 'Kommentar konnte nicht gelöscht werden.',
|
||||
'Do you really want to remove this comment?' => 'Soll dieser Kommentar wirklich gelöscht werden?',
|
||||
'Only administrators or the creator of the comment can access to this page.' => 'Nur Administratoren und der Ersteller des Kommentars könne diese Seite verwenden.',
|
||||
'Details' => 'Details',
|
||||
'Current password for the user "%s"' => 'Aktuelles Passwort für den Benutzer «%s»',
|
||||
'The current password is required' => 'Das aktuelle Passwort wird benötigt',
|
||||
'Wrong password' => 'Falsches Passwort',
|
||||
'Reset all tokens' => 'Alle Tokens zurücksetzten',
|
||||
'All tokens have been regenerated.' => 'Alle Tokens wurden zurückgesetzt.',
|
||||
'Unknown' => 'Unbekannt',
|
||||
'Last logins' => 'Letzte Anmeldungen',
|
||||
'Login date' => 'Anmeldedatum',
|
||||
'Authentication method' => 'Anmeldemethode',
|
||||
'IP address' => 'IP Adresse',
|
||||
'User agent' => 'User Agent',
|
||||
'Persistent connections' => 'Bestehende Verbindungen',
|
||||
'No session' => 'Keine Session',
|
||||
'Expiration date' => 'Ablaufdatum',
|
||||
'Remember Me' => 'Angemeldet bleiben',
|
||||
'Creation date' => 'Erstellungsdatum',
|
||||
'Filter by user' => 'Benutzer filtern',
|
||||
'Filter by due date' => 'Fälligkeit filtern',
|
||||
'Everybody' => 'Alle',
|
||||
'Open' => 'Offen',
|
||||
'Closed' => 'Abgeschlossen',
|
||||
'Search' => 'Suchen',
|
||||
'Nothing found.' => 'Nichts gefunden.',
|
||||
'Search in the project "%s"' => 'Suche in Projekt «%s»',
|
||||
'Due date' => 'Fälligkeitsdatum',
|
||||
'Others formats accepted: %s and %s' => 'Andere akzeptierte Formate: %s und %s',
|
||||
'Description' => 'Beschreibung',
|
||||
'%d comments' => '%d Kommentare',
|
||||
'%d comment' => '%d Kommentar',
|
||||
'Email address invalid' => 'Ungültige Email-Adresse',
|
||||
'Your Google Account is not linked anymore to your profile.' => 'Google Account nicht mehr mit dem Profil verbunden.',
|
||||
'Unable to unlink your Google Account.' => 'Trennung der Verbindung zum Google Account nicht möglich.',
|
||||
'Google authentication failed' => 'Zugang mit Google fehl geschlagen',
|
||||
'Unable to link your Google Account.' => 'Verbindung mit diesem Google Account nicht möglich.',
|
||||
'Your Google Account is linked to your profile successfully.' => 'Der Google Account wurde erfolgreich verbunden.',
|
||||
'Email' => 'Email',
|
||||
'Link my Google Account' => 'Verbinde meinen Google Account',
|
||||
'Unlink my Google Account' => 'Verbindung mit meinem Google Account trennen',
|
||||
'Login with my Google Account' => 'Anmelden mit meinem Google Account',
|
||||
'Project not found.' => 'Das Projekt wurde nicht gefunden.',
|
||||
'Task #%d' => 'Aufgabe #%d',
|
||||
'Task removed successfully.' => 'Aufgabe erfolgreich gelöscht.',
|
||||
'Unable to remove this task.' => 'Löschen der Aufgabe nicht möglich.',
|
||||
'Remove a task' => 'Aufgabe löschen',
|
||||
'Do you really want to remove this task: "%s"?' => 'Soll diese Aufgabe wirklich gelöscht werden: «%s»?',
|
||||
'Assign automatically a color based on a category' => 'Automatisch eine Farbe anhand der Kategorie vergeben',
|
||||
'Assign automatically a category based on a color' => 'Automatisch eine Kategorie anhand der Farbe vergeben',
|
||||
'Task creation or modification' => 'Erstellung oder Änderung einer Aufgabe',
|
||||
'Category' => 'Kategorie',
|
||||
'Category:' => 'Kategorie:',
|
||||
'Categories' => 'Kategorien',
|
||||
'Category not found.' => 'Kategorie nicht gefunden.',
|
||||
'Your category have been created successfully.' => 'Kategorie erfolgreich erstellt.',
|
||||
'Unable to create your category.' => 'Erstellung der Kategorie nicht möglich.',
|
||||
'Your category have been updated successfully.' => 'Kategorie erfolgreich aktualisiert.',
|
||||
'Unable to update your category.' => 'Änderung der Kategorie nicht möglich.',
|
||||
'Remove a category' => 'Kategorie löschen',
|
||||
'Category removed successfully.' => 'Kategorie erfolgreich gelöscht.',
|
||||
'Unable to remove this category.' => 'Löschen der Kategorie nicht möglich.',
|
||||
'Category modification for the project "%s"' => 'Kategorie für das Projekt «%s» bearbeiten',
|
||||
'Category Name' => 'Kategoriename',
|
||||
'Categories for the project "%s"' => 'Kategorien des Projektes «%s»',
|
||||
'Add a new category' => 'Neue Kategorie',
|
||||
'Do you really want to remove this category: "%s"?' => 'Soll diese Kategorie wirklich gelöscht werden: «%s»?',
|
||||
'Filter by category' => 'Kategorie filtern',
|
||||
'All categories' => 'Alle Kategorien',
|
||||
'No category' => 'keine Kategorie',
|
||||
'The name is required' => 'Der Name ist erforderlich',
|
||||
'Remove a file' => 'Datei löschen',
|
||||
'Unable to remove this file.' => 'Löschen der Datei nicht möglich.',
|
||||
'File removed successfully.' => 'Datei erfolgreich gelöscht.',
|
||||
'Attach a document' => 'Datei anhängen',
|
||||
'Do you really want to remove this file: "%s"?' => 'Soll diese Datei wirklich gelöscht werden: «%s»?',
|
||||
'open' => 'öffnen',
|
||||
'Attachments' => 'Anhänge',
|
||||
'Edit the task' => 'Aufgabe bearbeiten',
|
||||
'Edit the description' => 'Beschreibung bearbeiten',
|
||||
'Add a comment' => 'Kommentar hinzufügen',
|
||||
'Edit a comment' => 'Kommentar bearbeiten',
|
||||
'Summary' => 'Zusammenfassung',
|
||||
'Time tracking' => 'Zeiterfassung',
|
||||
'Estimate:' => 'Geschätzt:',
|
||||
'Spent:' => 'Aufgewendet:',
|
||||
'Do you really want to remove this sub-task?' => 'Soll diese Unteraufgabe wirklich gelöscht werden: «%s»?',
|
||||
'Remaining:' => 'Verbleibend:',
|
||||
'hours' => 'Stunden',
|
||||
'spent' => 'aufgewendet',
|
||||
'estimated' => 'geschätzt',
|
||||
'Sub-Tasks' => 'Unteraufgaben',
|
||||
'Add a sub-task' => 'Unteraufgabe anlegen',
|
||||
'Original Estimate' => 'Geschätzter Aufwand',
|
||||
'Create another sub-task' => 'Weitere Unteraufgabe anlegen',
|
||||
'Time Spent' => 'Aufgewendete Zeit',
|
||||
'Edit a sub-task' => 'Unteraufgabe bearbeiten',
|
||||
'Remove a sub-task' => 'Unteraufgabe löschen',
|
||||
'The time must be a numeric value' => 'Zeit nur als nummerische Angabe',
|
||||
'Todo' => 'Nicht gestartet',
|
||||
'In progress' => 'In Bearbeitung',
|
||||
'Done' => 'Erledigt',
|
||||
'Sub-task removed successfully.' => 'Unteraufgabe erfolgreich gelöscht.',
|
||||
'Unable to remove this sub-task.' => 'Löschen der Unteraufgabe nicht möglich.',
|
||||
'Sub-task updated successfully.' => 'Unteraufgabe erfolgreich aktualisiert.',
|
||||
'Unable to update your sub-task.' => 'Aktualisieren der Unteraufgabe nicht möglich.',
|
||||
'Unable to create your sub-task.' => 'Erstellen der Unteraufgabe nicht möglich.',
|
||||
'Sub-task added successfully.' => 'Unteraufgabe erfolgreich angelegt.',
|
||||
'Maximum size: ' => 'Maximalgröße: ',
|
||||
'Unable to upload the file.' => 'Hochladen der Datei nicht möglich.',
|
||||
'Display another project' => 'Zu Projekt wechseln...',
|
||||
'Your GitHub account was successfully linked to your profile.' => 'GitHub Account erfolgreich mit dem Profil verbunden.',
|
||||
'Unable to link your GitHub Account.' => 'Verbindung mit diesem GitHub Account nicht möglich.',
|
||||
'GitHub authentication failed' => 'Zugang mit GitHub fehl geschlagen',
|
||||
'Your GitHub account is no longer linked to your profile.' => 'GitHub Account nicht mehr mit dem Profil verbunden.',
|
||||
'Unable to unlink your GitHub Account.' => 'Trennung der Verbindung zum GitHub Account nicht möglich.',
|
||||
'Login with my GitHub Account' => 'Anmelden mit meinem GitHub Account',
|
||||
'Link my GitHub Account' => 'Mit meinem GitHub Account verbinden',
|
||||
'Unlink my GitHub Account' => 'Verbindung mit meinem GitHub Account trennen',
|
||||
'Created by %s' => 'Erstellt durch %s',
|
||||
'Last modified on %B %e, %G at %k:%M %p' => 'Letzte Änderung am %d.%m.%Y um %H:%M',
|
||||
);
|
389
sources/app/Locales/es_ES/translations.php
Normal file
389
sources/app/Locales/es_ES/translations.php
Normal file
|
@ -0,0 +1,389 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
'English' => 'Inglés',
|
||||
'French' => 'Francés',
|
||||
'Polish' => 'Polaco',
|
||||
'Portuguese (Brazilian)' => 'Portugués (Brasil)',
|
||||
'Spanish' => 'Español',
|
||||
// 'German' => '',
|
||||
// 'Chinese (Simplified)' => '',
|
||||
// 'Swedish' => 'Suèdois',
|
||||
'None' => 'Ninguno',
|
||||
'edit' => 'modificar',
|
||||
'Edit' => 'Modificar',
|
||||
'remove' => 'suprimir',
|
||||
'Remove' => 'Suprimir',
|
||||
'Update' => 'Actualizar',
|
||||
'Yes' => 'Sí',
|
||||
'No' => 'No',
|
||||
'cancel' => 'cancelar',
|
||||
'or' => 'o',
|
||||
'Yellow' => 'Amarillo',
|
||||
'Blue' => 'Azul',
|
||||
'Green' => 'Verde',
|
||||
'Purple' => 'Púrpura',
|
||||
'Red' => 'Rojo',
|
||||
'Orange' => 'Naranja',
|
||||
'Grey' => 'Gris',
|
||||
'Save' => 'Guardar',
|
||||
'Login' => 'Iniciar sesión (Ingresar)',
|
||||
'Official website:' => 'Página web oficial :',
|
||||
'Unassigned' => 'No asignado',
|
||||
'View this task' => 'Ver esta tarea',
|
||||
'Remove user' => 'Eliminar un usuario',
|
||||
'Do you really want to remove this user: "%s"?' => '¿De verdad que deseas suprimir a este usuario: « %s » ?',
|
||||
'New user' => 'Añadir un usuario',
|
||||
'All users' => 'Todos los usuarios',
|
||||
'Username' => 'Nombre de usuario',
|
||||
'Password' => 'Contraseña',
|
||||
'Default Project' => 'Proyecto por defecto',
|
||||
'Administrator' => 'Administrador',
|
||||
'Sign in' => 'Iniciar sesión',
|
||||
'Users' => 'Usuarios',
|
||||
'No user' => 'Ningún usuario',
|
||||
'Forbidden' => 'Acceso denegado',
|
||||
'Access Forbidden' => 'Acceso denegado',
|
||||
'Only administrators can access to this page.' => 'Solo los administradores pueden acceder a esta página.',
|
||||
'Edit user' => 'Editar un usuario',
|
||||
'Logout' => 'Salir',
|
||||
'Bad username or password' => 'Usuario o contraseña incorecto',
|
||||
'users' => 'usuarios',
|
||||
'projects' => 'proyectos',
|
||||
'Edit project' => 'Editar el proyecto',
|
||||
'Name' => 'Nombre',
|
||||
'Activated' => 'Activado',
|
||||
'Projects' => 'Proyectos',
|
||||
'No project' => 'Ningún proyecto',
|
||||
'Project' => 'Proyecto',
|
||||
'Status' => 'Estado',
|
||||
'Tasks' => 'Tareas',
|
||||
'Board' => 'Tablero',
|
||||
'Inactive' => 'Inactivo',
|
||||
'Active' => 'Activo',
|
||||
'Column %d' => 'Columna %d',
|
||||
'Add this column' => 'Añadir esta columna',
|
||||
'%d tasks on the board' => '%d tareas en el tablero',
|
||||
'%d tasks in total' => '%d tareas en total',
|
||||
'Unable to update this board.' => 'No se puede actualizar este tablero.',
|
||||
'Edit board' => 'Editar este tablero',
|
||||
'Disable' => 'Desactivar',
|
||||
'Enable' => 'Activar',
|
||||
'New project' => 'Nuevo proyecto',
|
||||
'Do you really want to remove this project: "%s"?' => '¿De verdad que deseas eliminar este proyecto: « %s » ?',
|
||||
'Remove project' => 'Suprimir el proyecto',
|
||||
'Boards' => 'Tableros',
|
||||
'Edit the board for "%s"' => 'Modificar el tablero por « %s »',
|
||||
'All projects' => 'Todos los proyectos',
|
||||
'Change columns' => 'Cambiar las columnas',
|
||||
'Add a new column' => 'Añadir una nueva columna',
|
||||
'Title' => 'Titulo',
|
||||
'Add Column' => 'Nueva columna',
|
||||
'Project "%s"' => 'Proyecto « %s »',
|
||||
'Nobody assigned' => 'Nadie asignado',
|
||||
'Assigned to %s' => 'Asignada a %s',
|
||||
'Remove a column' => 'Suprimir esta columna',
|
||||
'Remove a column from a board' => 'Suprimir una columna de un tablero',
|
||||
'Unable to remove this column.' => 'No se puede suprimir esta columna.',
|
||||
'Do you really want to remove this column: "%s"?' => '¿De vedad que deseas eliminar esta columna : « %s » ?',
|
||||
'This action will REMOVE ALL TASKS associated to this column!' => '¡Esta acción SUPRIMIRÁ TODAS LAS TAREAS asociadas a esta columna!',
|
||||
'Settings' => 'Preferencias',
|
||||
'Application settings' => 'Parámetros de la aplicación',
|
||||
'Language' => 'Idioma',
|
||||
'Webhooks token:' => 'Identificador (token) para los webhooks :',
|
||||
'More information' => 'Más informaciones',
|
||||
'Database size:' => 'Tamaño de la base de datos:',
|
||||
'Download the database' => 'Descargar la base de datos',
|
||||
'Optimize the database' => 'Optimizar la base de datos',
|
||||
'(VACUUM command)' => '(Comando VACUUM)',
|
||||
'(Gzip compressed Sqlite file)' => '(Archivo Sqlite comprimido en Gzip)',
|
||||
'User settings' => 'Parámetros de usuario',
|
||||
'My default project:' => 'Mi proyecto por defecto: ',
|
||||
'Close a task' => 'Cerrar una tarea',
|
||||
'Do you really want to close this task: "%s"?' => '¿Realmente desea cerrar esta tarea: « %s » ?',
|
||||
'Edit a task' => 'Editar una tarea',
|
||||
'Column' => 'Columna',
|
||||
'Color' => 'Color',
|
||||
'Assignee' => 'Persona asignada',
|
||||
'Create another task' => 'Crear una nueva tarea',
|
||||
'New task' => 'Nueva tarea',
|
||||
'Open a task' => 'Abrir una tarea',
|
||||
'Do you really want to open this task: "%s"?' => '¿Realmente desea abrir esta tarea: « %s » ?',
|
||||
'Back to the board' => 'Volver al tablero',
|
||||
'Created on %B %e, %G at %k:%M %p' => 'Creado el %d/%m/%Y a las %H:%M',
|
||||
'There is nobody assigned' => 'No hay nadie asignado a esta tarea',
|
||||
'Column on the board:' => 'Columna en el tablero: ',
|
||||
'Status is open' => 'Estado abierto',
|
||||
'Status is closed' => 'Estado cerrado',
|
||||
'Close this task' => 'Cerrar esta tarea',
|
||||
'Open this task' => 'Abrir esta tarea',
|
||||
'There is no description.' => 'No hay descripción.',
|
||||
'Add a new task' => 'Añadir una nueva tarea',
|
||||
'The username is required' => 'El nombre de usuario es obligatorio',
|
||||
'The maximum length is %d characters' => 'La longitud máxima es de %d caracteres',
|
||||
'The minimum length is %d characters' => 'La longitud mínima es de %d caracteres',
|
||||
'The password is required' => 'La contraseña es obligatoria',
|
||||
'This value must be an integer' => 'Este valor debe ser un entero',
|
||||
'The username must be unique' => 'El nombre de usuario debe ser único',
|
||||
'The username must be alphanumeric' => 'El nombre de usuario debe ser alfanumérico',
|
||||
'The user id is required' => 'El identificador del usuario es obligatorio',
|
||||
'Passwords doesn\'t matches' => 'Las contraseñas no corresponden',
|
||||
'The confirmation is required' => 'La confirmación es obligatoria',
|
||||
'The column is required' => 'La columna es obligatoria',
|
||||
'The project is required' => 'El proyecto es obligatorio',
|
||||
'The color is required' => 'El color es obligatorio',
|
||||
'The id is required' => 'El identificador es obligatorio',
|
||||
'The project id is required' => 'El identificador del proyecto es obligatorio',
|
||||
'The project name is required' => 'El nombre del proyecto es obligatorio',
|
||||
'This project must be unique' => 'El nombre del proyecto debe ser único',
|
||||
'The title is required' => 'El titulo es obligatorio',
|
||||
'The language is required' => 'El idioma es obligatorio',
|
||||
'There is no active project, the first step is to create a new project.' => 'No hay proyectos activados, la primera etapa consiste en crear un nuevo proyecto.',
|
||||
'Settings saved successfully.' => 'Parámetros guardados correctamente.',
|
||||
'Unable to save your settings.' => 'No se pueden guardar sus parámetros.',
|
||||
'Database optimization done.' => 'Optimización de la base de datos terminada.',
|
||||
'Your project have been created successfully.' => 'El proyecto ha sido creado correctamente.',
|
||||
'Unable to create your project.' => 'No se puede crear el proyecto.',
|
||||
'Project updated successfully.' => 'El proyecto ha sido actualizado correctamente.',
|
||||
'Unable to update this project.' => 'No se puede actualizar el proyecto.',
|
||||
'Unable to remove this project.' => 'No se puede suprimir este proyecto.',
|
||||
'Project removed successfully.' => 'El proyecto ha sido borrado correctamente.',
|
||||
'Project activated successfully.' => 'El proyecto ha sido activado correctamente.',
|
||||
'Unable to activate this project.' => 'No se puede activar el proyecto.',
|
||||
'Project disabled successfully.' => 'El proyecto ha sido desactivado correctamente.',
|
||||
'Unable to disable this project.' => 'No se puede desactivar el proyecto.',
|
||||
'Unable to open this task.' => 'No se puede abrir esta tarea.',
|
||||
'Task opened successfully.' => 'La tarea ha sido abierta correctamente.',
|
||||
'Unable to close this task.' => 'No se puede cerrar esta tarea.',
|
||||
'Task closed successfully.' => 'La tarea ha sido cerrada correctamente.',
|
||||
'Unable to update your task.' => 'No se puede modificar esta tarea.',
|
||||
'Task updated successfully.' => 'La tarea ha sido actualizada correctamente.',
|
||||
'Unable to create your task.' => 'No se puede crear esta tarea.',
|
||||
'Task created successfully.' => 'La tarea ha sido creada correctamente.',
|
||||
'User created successfully.' => 'El usuario ha sido creado correctamente.',
|
||||
'Unable to create your user.' => 'No se puede crear este usuario.',
|
||||
'User updated successfully.' => 'El usuario ha sido actualizado correctamente.',
|
||||
'Unable to update your user.' => 'No se puede actualizar este usuario.',
|
||||
'User removed successfully.' => 'El usuario ha sido creado correctamente.',
|
||||
'Unable to remove this user.' => 'No se puede crear este usuario.',
|
||||
'Board updated successfully.' => 'El tablero ha sido actualizado correctamente.',
|
||||
'Ready' => 'Listo',
|
||||
'Backlog' => 'En espera',
|
||||
'Work in progress' => 'En curso',
|
||||
'Done' => 'Terminado',
|
||||
'Application version:' => 'Versión de la aplicación:',
|
||||
'Completed on %B %e, %G at %k:%M %p' => 'Completado el %d/%m/%Y a las %H:%M',
|
||||
'%B %e, %G at %k:%M %p' => '%d/%m/%Y a las %H:%M',
|
||||
'Date created' => 'Fecha de creación',
|
||||
'Date completed' => 'Fecha de terminación',
|
||||
'Id' => 'Identificador',
|
||||
'No task' => 'Ninguna tarea',
|
||||
'Completed tasks' => 'Tareas completadas',
|
||||
'List of projects' => 'Lista de los proyectos',
|
||||
'Completed tasks for "%s"' => 'Tarea completada por « %s »',
|
||||
'%d closed tasks' => '%d tareas completadas',
|
||||
'no task for this project' => 'ninguna tarea para este proyecto',
|
||||
'Public link' => 'Enlace público',
|
||||
'There is no column in your project!' => '¡No hay ninguna columna para este proyecto!',
|
||||
'Change assignee' => 'Cambiar la persona asignada',
|
||||
'Change assignee for the task "%s"' => 'Cambiar la persona asignada por la tarea « %s »',
|
||||
'Timezone' => 'Zona horaria',
|
||||
'Sorry, I didn\'t found this information in my database!' => 'Lo siento no he encontrado información en la base de datos!',
|
||||
'Page not found' => 'Página no encontrada',
|
||||
'Story Points' => 'Complejidad',
|
||||
'limit' => 'límite',
|
||||
'Task limit' => 'Número máximo de tareas',
|
||||
'This value must be greater than %d' => 'Este valor no debe de ser más grande que %d',
|
||||
'Edit project access list' => 'Editar los permisos del proyecto',
|
||||
'Edit users access' => 'Editar los permisos de usuario',
|
||||
'Allow this user' => 'Autorizar este usuario',
|
||||
'Project access list for "%s"' => 'Permisos del proyecto « %s »',
|
||||
'Only those users have access to this project:' => 'Solo estos usuarios tienen acceso a este proyecto:',
|
||||
'Don\'t forget that administrators have access to everything.' => 'No olvide que los administradores tienen acceso a todo.',
|
||||
'revoke' => 'revocar',
|
||||
'List of authorized users' => 'Lista de los usuarios autorizados',
|
||||
'User' => 'Usuario',
|
||||
'Everybody have access to this project.' => 'Todo el mundo tiene acceso al proyecto.',
|
||||
'You are not allowed to access to this project.' => 'No está autorizado a acceder a este proyecto.',
|
||||
'Comments' => 'Comentarios',
|
||||
'Post comment' => 'Commentar',
|
||||
'Write your text in Markdown' => 'Redacta el texto en Markdown',
|
||||
'Leave a comment' => 'Dejar un comentario',
|
||||
'Comment is required' => 'El comentario es obligatorio',
|
||||
'Leave a description' => 'Dejar una descripción',
|
||||
'Comment added successfully.' => 'El comentario ha sido añadido correctamente.',
|
||||
'Unable to create your comment.' => 'No se puede crear este comentario.',
|
||||
'The description is required' => 'La descripción es obligatoria',
|
||||
'Edit this task' => 'Editar esta tarea',
|
||||
'Due Date' => 'Fecha límite',
|
||||
'm/d/Y' => 'd/m/Y', // Date format parsed with php
|
||||
'month/day/year' => 'día/mes/año', // Help shown to the user
|
||||
'Invalid date' => 'Fecha no válida',
|
||||
'Must be done before %B %e, %G' => 'Debe de estar hecho antes del %d/%m/%Y',
|
||||
'%B %e, %G' => '%d/%m/%Y',
|
||||
'Automatic actions' => 'Acciones automatizadas',
|
||||
'Your automatic action have been created successfully.' => 'La acción automatizada ha sido creada correctamente.',
|
||||
'Unable to create your automatic action.' => 'No se puede crear esta acción automatizada.',
|
||||
'Remove an action' => 'Suprimir una acción',
|
||||
'Unable to remove this action.' => 'No se puede suprimir esta accción.',
|
||||
'Action removed successfully.' => 'La acción ha sido borrada correctamente.',
|
||||
'Automatic actions for the project "%s"' => 'Acciones automatizadas para este proyecto « %s »',
|
||||
'Defined actions' => 'Acciones definidas',
|
||||
'Event name' => 'Nombre del evento',
|
||||
'Action name' => 'Nombre de la acción',
|
||||
'Action parameters' => 'Parámetros de la acción',
|
||||
'Action' => 'Acción',
|
||||
'Event' => 'Evento',
|
||||
'When the selected event occurs execute the corresponding action.' => 'Cuando tiene lugar el evento seleccionado, ejecutar la acción correspondiente.',
|
||||
'Next step' => 'Etapa siguiente',
|
||||
'Define action parameters' => 'Definición de los parametros de la acción',
|
||||
'Save this action' => 'Guardar esta acción',
|
||||
'Do you really want to remove this action: "%s"?' => '¿Realmente desea suprimir esta acción « %s » ?',
|
||||
'Remove an automatic action' => 'Suprimir una acción automatizada',
|
||||
'Close the task' => 'Cerrar esta tarea',
|
||||
'Assign the task to a specific user' => 'Asignar una tarea a un usuario especifico',
|
||||
'Assign the task to the person who does the action' => 'Asignar la tarea al usuario que hace la acción',
|
||||
'Duplicate the task to another project' => 'Duplicar la tarea a otro proyecto',
|
||||
'Move a task to another column' => 'Mover una tarea a otra columna',
|
||||
'Move a task to another position in the same column' => 'Mover una tarea a otra posición en la misma columna',
|
||||
'Task modification' => 'Modificación de una tarea',
|
||||
'Task creation' => 'Creación de una tarea',
|
||||
'Open a closed task' => 'Abrir una tarea cerrada',
|
||||
'Closing a task' => 'Cerrar una tarea',
|
||||
'Assign a color to a specific user' => 'Asignar un color a un usuario específico',
|
||||
'Column title' => 'Título de la columna',
|
||||
'Position' => 'Posición',
|
||||
'Move Up' => 'Mover hacia arriba',
|
||||
'Move Down' => 'Mover hacia abajo',
|
||||
'Duplicate to another project' => 'Duplicar a otro proyecto',
|
||||
'Duplicate' => 'Duplicar',
|
||||
'link' => 'enlace',
|
||||
'Update this comment' => 'Actualizar este comentario',
|
||||
'Comment updated successfully.' => 'El comentario ha sido actualizado correctamente.',
|
||||
'Unable to update your comment.' => 'No se puede actualizar este comentario.',
|
||||
'Remove a comment' => 'Suprimir un comentario',
|
||||
'Comment removed successfully.' => 'El comentario ha sido suprimido correctamente.',
|
||||
'Unable to remove this comment.' => 'No se puede suprimir este comentario.',
|
||||
'Do you really want to remove this comment?' => '¿Desea suprimir este comentario?',
|
||||
'Only administrators or the creator of the comment can access to this page.' => 'Sólo los administradores o el autor del comentario tienen acceso a esta página.',
|
||||
'Details' => 'Detalles',
|
||||
'Current password for the user "%s"' => 'Contraseña actual para el usuario: « %s »',
|
||||
'The current password is required' => 'La contraseña es obligatoria',
|
||||
'Wrong password' => 'contraseña incorrecta',
|
||||
'Reset all tokens' => 'Reiniciar las fichas (tokens) de seguridad ',
|
||||
'All tokens have been regenerated.' => 'Todas las fichas (tokens) han sido regeneradas.',
|
||||
'Unknown' => 'Desconocido',
|
||||
'Last logins' => 'Últimos ingresos',
|
||||
'Login date' => 'Fecha de ingreso',
|
||||
'Authentication method' => 'Método de autenticación',
|
||||
'IP address' => 'Dirección IP',
|
||||
'User agent' => 'Agente de usuario',
|
||||
'Persistent connections' => 'Conexión persistente',
|
||||
'No session' => 'No existe sesión',
|
||||
'Expiration date' => 'Fecha de expiración',
|
||||
'Remember Me' => 'Recuérdame',
|
||||
'Creation date' => 'Fecha de creación',
|
||||
'Filter by user' => 'Filtrado mediante usuario',
|
||||
'Filter by due date' => 'Filtrado mediante fecha límite',
|
||||
'Everybody' => 'Todo el mundo',
|
||||
'Open' => 'Abierto',
|
||||
'Closed' => 'Cerrado',
|
||||
'Search' => 'Buscar',
|
||||
'Nothing found.' => 'Nada hallado.',
|
||||
'Search in the project "%s"' => 'Buscar en el proyecto "%s"',
|
||||
'Due date' => 'Fecha límite',
|
||||
'Others formats accepted: %s and %s' => 'Otros formatos aceptados: %s y %s',
|
||||
'Description' => 'Descripción',
|
||||
'%d comments' => '%d comentarios',
|
||||
'%d comment' => '%d comentario',
|
||||
'Email address invalid' => 'Dirección de correo inválida',
|
||||
'Your Google Account is not linked anymore to your profile.' => 'Tu Cuenta en Google ya no se encuentra enlazada con tu perfil',
|
||||
'Unable to unlink your Google Account.' => 'No puedo desenlazar tu Cuenta en Google.',
|
||||
'Google authentication failed' => 'Ha fallado tu autenticación en Google',
|
||||
'Unable to link your Google Account.' => 'No puedo enlazar con tu Cuenta en Google.',
|
||||
'Your Google Account is linked to your profile successfully.' => 'Se ha enlazado correctamente tu Cuenta en Google con tu perfil.',
|
||||
'Email' => 'Correo',
|
||||
'Link my Google Account' => 'Enlaza con mi Cuenta en Google',
|
||||
'Unlink my Google Account' => 'Desenlaza con mi Cuenta en Google',
|
||||
'Login with my Google Account' => 'Ingresa con mi Cuenta en Google',
|
||||
'Project not found.' => 'Proyecto no hallado.',
|
||||
'Task #%d' => 'Tarea número %d',
|
||||
'Task removed successfully.' => 'Tarea suprimida correctamente.',
|
||||
'Unable to remove this task.' => 'No pude suprimir esta tarea.',
|
||||
'Remove a task' => 'Borrar una tarea',
|
||||
'Do you really want to remove this task: "%s"?' => '¿De verdad que quieres suprimir esta tarea: "%s"?',
|
||||
'Assign automatically a color based on a category' => 'Asignar un color de forma automática basándose en la categoría',
|
||||
'Assign automatically a category based on a color' => 'Asignar una categoría de forma automática basándose en el color',
|
||||
'Task creation or modification' => 'Creación o Edición de Tarea',
|
||||
'Category' => 'Categoría',
|
||||
'Category:' => 'Categoría:',
|
||||
'Categories' => 'Categorías',
|
||||
'Category not found.' => 'Categoría no hallada.',
|
||||
'Your category have been created successfully.' => 'Se ha creado tu categoría correctamente.',
|
||||
'Unable to create your category.' => 'No pude crear tu categoría.',
|
||||
'Your category have been updated successfully.' => 'Se ha actualizado tu categoría correctamente.',
|
||||
'Unable to update your category.' => 'No pude actualizar tu categoría.',
|
||||
'Remove a category' => 'Suprimir una categoría',
|
||||
'Category removed successfully.' => 'Categoría suprimida correctamente.',
|
||||
'Unable to remove this category.' => 'No pude suprimir esta categoría.',
|
||||
'Category modification for the project "%s"' => 'Modificación de categoría pra el proyecto "%s"',
|
||||
'Category Name' => 'Nombre de Categoría',
|
||||
'Categories for the project "%s"' => 'Categorías para el proyecto',
|
||||
'Add a new category' => 'Añadir una nueva categoría',
|
||||
'Do you really want to remove this category: "%s"?' => '¿De verdad que quieres suprimir esta categoría: "%s"?',
|
||||
'Filter by category' => 'Filtrar mendiante categoría',
|
||||
'All categories' => 'Todas las categorías',
|
||||
'No category' => 'Sin categoría',
|
||||
'The name is required' => 'El nombre es obligatorio',
|
||||
'Remove a file' => 'Borrar un fichero',
|
||||
'Unable to remove this file.' => 'No pude borrar este fichero.',
|
||||
'File removed successfully.' => 'Fichero borrado correctamente.',
|
||||
'Attach a document' => 'Adjuntar un documento',
|
||||
'Do you really want to remove this file: "%s"?' => '¿De verdad que quieres borrar este fichero: "%s"?',
|
||||
'open' => 'abrir',
|
||||
'Attachments' => 'Adjuntos',
|
||||
'Edit the task' => 'Editar la tarea',
|
||||
'Edit the description' => 'Editar la descripción',
|
||||
'Add a comment' => 'Añadir un comentario',
|
||||
'Edit a comment' => 'Editar un comentario',
|
||||
'Summary' => 'Resumen',
|
||||
'Time tracking' => 'Seguimiento temporal',
|
||||
'Estimate:' => 'Estimado:',
|
||||
'Spent:' => 'Transcurrido:',
|
||||
'Do you really want to remove this sub-task?' => '¿De verdad que quieres suprimir esta sub-tarea?',
|
||||
'Remaining:' => 'Quedando',
|
||||
'hours' => 'horas',
|
||||
'spent' => 'transcurrido',
|
||||
'estimated' => 'estimado',
|
||||
'Sub-Tasks' => 'Sub-Tareas',
|
||||
'Add a sub-task' => 'Añadir una sub-tarea',
|
||||
'Original Estimate' => 'Estimado Original',
|
||||
'Create another sub-task' => 'Crear otra sub-tarea',
|
||||
'Time Spent' => 'Tiempo Transcurrido',
|
||||
'Edit a sub-task' => 'Editar una sub-tarea',
|
||||
'Remove a sub-task' => 'Suprimir una sub-tarea',
|
||||
'The time must be a numeric value' => 'El tiempo debe de ser un valor numérico',
|
||||
'Todo' => 'Por hacer',
|
||||
'In progress' => 'En progreso',
|
||||
'Done' => 'Hecho',
|
||||
'Sub-task removed successfully.' => 'Sub-tarea suprimida correctamente.',
|
||||
'Unable to remove this sub-task.' => 'No pude suprimir esta sub-tarea.',
|
||||
'Sub-task updated successfully.' => 'Sub-tarea actualizada correctamente.',
|
||||
'Unable to update your sub-task.' => 'No pude actualizar tu sub-tarea.',
|
||||
'Unable to create your sub-task.' => 'No pude crear tu sub-tarea.',
|
||||
'Sub-task added successfully.' => 'Sub-tarea añadida correctamente.',
|
||||
'Maximum size: ' => 'Tamaño máximo',
|
||||
'Unable to upload the file.' => 'No pude cargar el fichero.',
|
||||
'Actions' => 'Acciones',
|
||||
// 'Display another project' => '',
|
||||
// 'Your GitHub account was successfully linked to your profile.' => '',
|
||||
// 'Unable to link your GitHub Account.' => '',
|
||||
// 'GitHub authentication failed' => '',
|
||||
// 'Your GitHub account is no longer linked to your profile.' => '',
|
||||
// 'Unable to unlink your GitHub Account.' => '',
|
||||
// 'Login with my GitHub Account' => '',
|
||||
// 'Link my GitHub Account' => '',
|
||||
// 'Unlink my GitHub Account' => '',
|
||||
// 'Created by %s' => 'Créé par %s',
|
||||
// 'Last modified on %B %e, %G at %k:%M %p' => '',
|
||||
);
|
387
sources/app/Locales/fr_FR/translations.php
Normal file
387
sources/app/Locales/fr_FR/translations.php
Normal file
|
@ -0,0 +1,387 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
'English' => 'Anglais',
|
||||
'French' => 'Français',
|
||||
'Polish' => 'Polonais',
|
||||
'Portuguese (Brazilian)' => 'Portugais (Brésil)',
|
||||
'Spanish' => 'Espagnol',
|
||||
'German' => 'Allemand',
|
||||
'Chinese (Simplified)' => 'Chinois simplifié',
|
||||
'Swedish' => 'Suèdois',
|
||||
'None' => 'Aucun',
|
||||
'edit' => 'modifier',
|
||||
'Edit' => 'Modifier',
|
||||
'remove' => 'supprimer',
|
||||
'Remove' => 'Supprimer',
|
||||
'Update' => 'Mettre à jour',
|
||||
'Yes' => 'Oui',
|
||||
'No' => 'Non',
|
||||
'cancel' => 'annuler',
|
||||
'or' => 'ou',
|
||||
'Yellow' => 'Jaune',
|
||||
'Blue' => 'Bleu',
|
||||
'Green' => 'Vert',
|
||||
'Purple' => 'Violet',
|
||||
'Red' => 'Rouge',
|
||||
'Orange' => 'Orange',
|
||||
'Grey' => 'Gris',
|
||||
'Save' => 'Enregistrer',
|
||||
'Login' => 'Connexion',
|
||||
'Official website:' => 'Site web officiel :',
|
||||
'Unassigned' => 'Non assigné',
|
||||
'View this task' => 'Voir cette tâche',
|
||||
'Remove user' => 'Supprimer un utilisateur',
|
||||
'Do you really want to remove this user: "%s"?' => 'Voulez-vous vraiment supprimer cet utilisateur : « %s » ?',
|
||||
'New user' => 'Ajouter un utilisateur',
|
||||
'All users' => 'Tous les utilisateurs',
|
||||
'Username' => 'Identifiant',
|
||||
'Password' => 'Mot de passe',
|
||||
'Default Project' => 'Projet par défaut',
|
||||
'Administrator' => 'Administrateur',
|
||||
'Sign in' => 'Connexion',
|
||||
'Users' => 'Utilisateurs',
|
||||
'No user' => 'Aucun utilisateur',
|
||||
'Forbidden' => 'Accès interdit',
|
||||
'Access Forbidden' => 'Accès interdit',
|
||||
'Only administrators can access to this page.' => 'Uniquement les administrateurs peuvent accéder à cette page.',
|
||||
'Edit user' => 'Modifier un utilisateur',
|
||||
'Logout' => 'Déconnexion',
|
||||
'Bad username or password' => 'Identifiant ou mot de passe incorrect',
|
||||
'users' => 'utilisateurs',
|
||||
'projects' => 'projets',
|
||||
'Edit project' => 'Modifier le projet',
|
||||
'Name' => 'Nom',
|
||||
'Activated' => 'Actif',
|
||||
'Projects' => 'Projets',
|
||||
'No project' => 'Aucun projet',
|
||||
'Project' => 'Projet',
|
||||
'Status' => 'État',
|
||||
'Tasks' => 'Tâches',
|
||||
'Board' => 'Tableau',
|
||||
'Inactive' => 'Inactif',
|
||||
'Active' => 'Actif',
|
||||
'Column %d' => 'Colonne %d',
|
||||
'Add this column' => 'Ajouter cette colonne',
|
||||
'%d tasks on the board' => '%d tâches sur le tableau',
|
||||
'%d tasks in total' => '%d tâches au total',
|
||||
'Unable to update this board.' => 'Impossible de mettre à jour ce tableau.',
|
||||
'Edit board' => 'Modifier le tableau',
|
||||
'Disable' => 'Désactiver',
|
||||
'Enable' => 'Activer',
|
||||
'New project' => 'Nouveau projet',
|
||||
'Do you really want to remove this project: "%s"?' => 'Voulez-vous vraiment supprimer ce projet : « %s » ?',
|
||||
'Remove project' => 'Supprimer le projet',
|
||||
'Boards' => 'Tableaux',
|
||||
'Edit the board for "%s"' => 'Modifier le tableau pour « %s »',
|
||||
'All projects' => 'Tous les projets',
|
||||
'Change columns' => 'Changer les colonnes',
|
||||
'Add a new column' => 'Ajouter une nouvelle colonne',
|
||||
'Title' => 'Titre',
|
||||
'Add Column' => 'Nouvelle colonne',
|
||||
'Project "%s"' => 'Projet « %s »',
|
||||
'Nobody assigned' => 'Personne assigné',
|
||||
'Assigned to %s' => 'Assigné à %s',
|
||||
'Remove a column' => 'Supprimer une colonne',
|
||||
'Remove a column from a board' => 'Supprimer une colonne d\'un tableau',
|
||||
'Unable to remove this column.' => 'Impossible de supprimer cette colonne.',
|
||||
'Do you really want to remove this column: "%s"?' => 'Voulez vraiment supprimer cette colonne : « %s » ?',
|
||||
'This action will REMOVE ALL TASKS associated to this column!' => 'Cette action va supprimer toutes les tâches associées à cette colonne !',
|
||||
'Settings' => 'Préférences',
|
||||
'Application settings' => 'Paramètres de l\'application',
|
||||
'Language' => 'Langue',
|
||||
'Webhooks token:' => 'Jeton de securité pour les webhooks :',
|
||||
'More information' => 'Plus d\'informations',
|
||||
'Database size:' => 'Taille de la base de données :',
|
||||
'Download the database' => 'Télécharger la base de données',
|
||||
'Optimize the database' => 'Optimiser la base de données',
|
||||
'(VACUUM command)' => '(Commande VACUUM)',
|
||||
'(Gzip compressed Sqlite file)' => '(Fichier Sqlite compressé en Gzip)',
|
||||
'User settings' => 'Paramètres utilisateur',
|
||||
'My default project:' => 'Mon projet par défaut : ',
|
||||
'Close a task' => 'Fermer une tâche',
|
||||
'Do you really want to close this task: "%s"?' => 'Voulez-vous vraiment fermer cettre tâche : « %s » ?',
|
||||
'Edit a task' => 'Modifier une tâche',
|
||||
'Column' => 'Colonne',
|
||||
'Color' => 'Couleur',
|
||||
'Assignee' => 'Personne assignée',
|
||||
'Create another task' => 'Créer une autre tâche',
|
||||
'New task' => 'Nouvelle tâche',
|
||||
'Open a task' => 'Ouvrir une tâche',
|
||||
'Do you really want to open this task: "%s"?' => 'Voulez-vous vraiment ouvrir cette tâche : « %s » ?',
|
||||
'Back to the board' => 'Retour au tableau',
|
||||
'Created on %B %e, %G at %k:%M %p' => 'Créé le %d/%m/%Y à %H:%M',
|
||||
'There is nobody assigned' => 'Il n\'y a personne d\'assigné à cette tâche',
|
||||
'Column on the board:' => 'Colonne sur le tableau : ',
|
||||
'Status is open' => 'État ouvert',
|
||||
'Status is closed' => 'État fermé',
|
||||
'Close this task' => 'Fermer cette tâche',
|
||||
'Open this task' => 'Ouvrir cette tâche',
|
||||
'There is no description.' => 'Il n\'y a pas de description.',
|
||||
'Add a new task' => 'Ajouter une nouvelle tâche',
|
||||
'The username is required' => 'Le nom d\'utilisateur est obligatoire',
|
||||
'The maximum length is %d characters' => 'La longueur maximale est de %d caractères',
|
||||
'The minimum length is %d characters' => 'La longueur minimale est de %d caractères',
|
||||
'The password is required' => 'Le mot de passe est obligatoire',
|
||||
'This value must be an integer' => 'Cette valeur doit être un entier',
|
||||
'The username must be unique' => 'Le nom d\'utilisateur doit être unique',
|
||||
'The username must be alphanumeric' => 'Le nom d\'utilisateur doit être alpha-numérique',
|
||||
'The user id is required' => 'L\'id de l\'utilisateur est obligatoire',
|
||||
'Passwords don\'t match' => 'Les mots de passe ne correspondent pas',
|
||||
'The confirmation is required' => 'Le confirmation est requise',
|
||||
'The column is required' => 'La colonne est obligatoire',
|
||||
'The project is required' => 'Le projet est obligatoire',
|
||||
'The color is required' => 'La couleur est obligatoire',
|
||||
'The id is required' => 'L\'identifiant est obligatoire',
|
||||
'The project id is required' => 'L\'identifiant du projet est obligatoire',
|
||||
'The project name is required' => 'Le nom du projet est obligatoire',
|
||||
'This project must be unique' => 'Le nom du projet doit être unique',
|
||||
'The title is required' => 'Le titre est obligatoire',
|
||||
'The language is required' => 'La langue est obligatoire',
|
||||
'There is no active project, the first step is to create a new project.' => 'Il n\'y a aucun projet actif, la première étape est de créer un nouveau projet.',
|
||||
'Settings saved successfully.' => 'Paramètres sauvegardés avec succès.',
|
||||
'Unable to save your settings.' => 'Impossible de sauvegarder vos réglages.',
|
||||
'Database optimization done.' => 'Optmisation de la base de données terminée.',
|
||||
'Your project have been created successfully.' => 'Votre projet a été créé avec succès.',
|
||||
'Unable to create your project.' => 'Impossible de créer un projet.',
|
||||
'Project updated successfully.' => 'Votre projet a été mis à jour avec succès.',
|
||||
'Unable to update this project.' => 'Impossible de mettre à jour ce projet.',
|
||||
'Unable to remove this project.' => 'Impossible de supprimer ce projet.',
|
||||
'Project removed successfully.' => 'Votre projet a été supprimé avec succès.',
|
||||
'Project activated successfully.' => 'Votre projet a été activé avec succès.',
|
||||
'Unable to activate this project.' => 'Impossible d\'activer ce projet.',
|
||||
'Project disabled successfully.' => 'Votre projet a été désactivé avec succès.',
|
||||
'Unable to disable this project.' => 'Impossible de désactiver ce projet.',
|
||||
'Unable to open this task.' => 'Impossible d\'ouvrir cette tâche.',
|
||||
'Task opened successfully.' => 'Tâche ouverte avec succès.',
|
||||
'Unable to close this task.' => 'Impossible de fermer cette tâche.',
|
||||
'Task closed successfully.' => 'Tâche fermé avec succès.',
|
||||
'Unable to update your task.' => 'Impossible de modifier cette tâche.',
|
||||
'Task updated successfully.' => 'Tâche mise à jour avec succès.',
|
||||
'Unable to create your task.' => 'Impossible de créer cette tâche.',
|
||||
'Task created successfully.' => 'Tâche créée avec succès.',
|
||||
'User created successfully.' => 'Utilisateur créé avec succès.',
|
||||
'Unable to create your user.' => 'Impossible de créer cet utilisateur.',
|
||||
'User updated successfully.' => 'Utilisateur mis à jour avec succès.',
|
||||
'Unable to update your user.' => 'Impossible de mettre à jour cet utilisateur.',
|
||||
'User removed successfully.' => 'Utilisateur supprimé avec succès.',
|
||||
'Unable to remove this user.' => 'Impossible de supprimer cet utilisateur.',
|
||||
'Board updated successfully.' => 'Tableau mis à jour avec succès.',
|
||||
'Ready' => 'Prêt',
|
||||
'Backlog' => 'En attente',
|
||||
'Work in progress' => 'En cours',
|
||||
'Done' => 'Terminé',
|
||||
'Application version:' => 'Version de l\'application :',
|
||||
'Completed on %B %e, %G at %k:%M %p' => 'Terminé le %d/%m/%Y à %H:%M',
|
||||
'%B %e, %G at %k:%M %p' => '%d/%m/%Y à %H:%M',
|
||||
'Date created' => 'Date de création',
|
||||
'Date completed' => 'Date de clôture',
|
||||
'Id' => 'Identifiant',
|
||||
'No task' => 'Aucune tâche',
|
||||
'Completed tasks' => 'Tâches terminées',
|
||||
'List of projects' => 'Liste des projets',
|
||||
'Completed tasks for "%s"' => 'Tâches terminées pour « %s »',
|
||||
'%d closed tasks' => '%d tâches terminées',
|
||||
'no task for this project' => 'aucune tâche pour ce projet',
|
||||
'Public link' => 'Accès public',
|
||||
'There is no column in your project!' => 'Il n\'y a aucune colonne dans votre projet !',
|
||||
'Change assignee' => 'Changer la personne assignée',
|
||||
'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »',
|
||||
'Timezone' => 'Fuseau horaire',
|
||||
'Sorry, I didn\'t found this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
|
||||
'Page not found' => 'Page introuvable',
|
||||
'Story Points' => 'Complexité',
|
||||
'limit' => 'limite',
|
||||
'Task limit' => 'Nombre maximum de tâches',
|
||||
'This value must be greater than %d' => 'Cette valeur doit être plus grande que %d',
|
||||
'Edit project access list' => 'Modifier l\'accès au projet',
|
||||
'Edit users access' => 'Modifier les utilisateurs autorisés',
|
||||
'Allow this user' => 'Autoriser cet utilisateur',
|
||||
'Project access list for "%s"' => 'Liste des accès au projet « %s »',
|
||||
'Only those users have access to this project:' => 'Seulement ces utilisateurs ont accès à ce projet :',
|
||||
'Don\'t forget that administrators have access to everything.' => 'N\'oubliez pas que les administrateurs ont accès à tout.',
|
||||
'revoke' => 'révoquer',
|
||||
'List of authorized users' => 'Liste des utilisateurs autorisés',
|
||||
'User' => 'Utilisateur',
|
||||
'Everybody have access to this project.' => 'Tout le monde a accès au projet.',
|
||||
'You are not allowed to access to this project.' => 'Vous n\'êtes pas autorisé à accéder à ce projet.',
|
||||
'Comments' => 'Commentaires',
|
||||
'Post comment' => 'Commenter',
|
||||
'Write your text in Markdown' => 'Écrivez votre texte en Markdown',
|
||||
'Leave a comment' => 'Laissez un commentaire',
|
||||
'Comment is required' => 'Le commentaire est obligatoire',
|
||||
'Leave a description' => 'Laissez une description',
|
||||
'Comment added successfully.' => 'Commentaire ajouté avec succès.',
|
||||
'Unable to create your comment.' => 'Impossible de sauvegarder votre commentaire.',
|
||||
'The description is required' => 'La description est obligatoire',
|
||||
'Edit this task' => 'Modifier cette tâche',
|
||||
'Due Date' => 'Date d\'échéance',
|
||||
'm/d/Y' => 'd/m/Y', // Date format parsed with php
|
||||
'month/day/year' => 'jour/mois/année', // Help shown to the user
|
||||
'Invalid date' => 'Date invalide',
|
||||
'Must be done before %B %e, %G' => 'Doit être fait avant le %d/%m/%Y',
|
||||
'%B %e, %G' => '%d/%m/%Y',
|
||||
'Automatic actions' => 'Actions automatisées',
|
||||
'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajouté avec succès.',
|
||||
'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.',
|
||||
'Remove an action' => 'Supprimer une action',
|
||||
'Unable to remove this action.' => 'Impossible de supprimer cette action',
|
||||
'Action removed successfully.' => 'Action supprimée avec succès.',
|
||||
'Automatic actions for the project "%s"' => 'Actions automatisées pour le projet « %s »',
|
||||
'Defined actions' => 'Actions définies',
|
||||
'Event name' => 'Nom de l\'événement',
|
||||
'Action name' => 'Nom de l\'action',
|
||||
'Action parameters' => 'Paramètres de l\'action',
|
||||
'Action' => 'Action',
|
||||
'Event' => 'Événement',
|
||||
'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'événement sélectionné se déclenche, executer l\'action correspondante.',
|
||||
'Next step' => 'Étape suivante',
|
||||
'Define action parameters' => 'Définition des paramètres de l\'action',
|
||||
'Save this action' => 'Sauvegarder cette action',
|
||||
'Do you really want to remove this action: "%s"?' => 'Voulez-vous vraiment supprimer cette action « %s » ?',
|
||||
'Remove an automatic action' => 'Supprimer une action automatisée',
|
||||
'Close the task' => 'Fermer cette tâche',
|
||||
'Assign the task to a specific user' => 'Assigner la tâche à un utilisateur spécifique',
|
||||
'Assign the task to the person who does the action' => 'Assigner la tâche à la personne qui fait l\'action',
|
||||
'Duplicate the task to another project' => 'Dupliquer la tâche vers un autre projet',
|
||||
'Move a task to another column' => 'Déplacement d\'une tâche vers un autre colonne',
|
||||
'Move a task to another position in the same column' => 'Déplacement d\'une tâche à une autre position mais dans la même colonne',
|
||||
'Task modification' => 'Modification d\'une tâche',
|
||||
'Task creation' => 'Création d\'une tâche',
|
||||
'Open a closed task' => 'Ouverture d\'une tâche fermée',
|
||||
'Closing a task' => 'Fermeture d\'une tâche',
|
||||
'Assign a color to a specific user' => 'Assigner une couleur à un utilisateur',
|
||||
'Column title' => 'Titre de la colonne',
|
||||
'Position' => 'Position',
|
||||
'Move Up' => 'Déplacer vers le haut',
|
||||
'Move Down' => 'Déplacer vers le bas',
|
||||
'Duplicate to another project' => 'Dupliquer dans un autre projet',
|
||||
'Duplicate' => 'Dupliquer',
|
||||
'link' => 'lien',
|
||||
'Update this comment' => 'Mettre à jour ce commentaire',
|
||||
'Comment updated successfully.' => 'Commentaire mis à jour avec succès.',
|
||||
'Unable to update your comment.' => 'Impossible de supprimer votre commentaire.',
|
||||
'Remove a comment' => 'Supprimer un commentaire',
|
||||
'Comment removed successfully.' => 'Commentaire supprimé avec succès.',
|
||||
'Unable to remove this comment.' => 'Impossible de supprimer ce commentaire.',
|
||||
'Do you really want to remove this comment?' => 'Voulez-vous vraiment supprimer ce commentaire ?',
|
||||
'Only administrators or the creator of the comment can access to this page.' => 'Uniquement les administrateurs ou le créateur du commentaire peuvent accéder à cette page.',
|
||||
'Details' => 'Détails',
|
||||
'Current password for the user "%s"' => 'Mot de passe actuel pour l\'utilisateur « %s »',
|
||||
'The current password is required' => 'Le mot de passe actuel est obligatoire',
|
||||
'Wrong password' => 'Mauvais mot de passe',
|
||||
'Reset all tokens' => 'Réinitialiser tous les jetons de sécurité',
|
||||
'All tokens have been regenerated.' => 'Tous les jetons de sécurité ont été réinitialisés.',
|
||||
'Unknown' => 'Inconnu',
|
||||
'Last logins' => 'Dernières connexions',
|
||||
'Login date' => 'Date de connexion',
|
||||
'Authentication method' => 'Méthode d\'authentification',
|
||||
'IP address' => 'Adresse IP',
|
||||
'User agent' => 'Agent utilisateur',
|
||||
'Persistent connections' => 'Connexions persistantes',
|
||||
'No session' => 'Aucune session',
|
||||
'Expiration date' => 'Date d\'expiration',
|
||||
'Remember Me' => 'Connexion automatique',
|
||||
'Creation date' => 'Date de création',
|
||||
'Filter by user' => 'Filtrer par utilisateur',
|
||||
'Filter by due date' => 'Filtrer par date d\'échéance',
|
||||
'Everybody' => 'Tout le monde',
|
||||
'Open' => 'Ouvert',
|
||||
'Closed' => 'Fermé',
|
||||
'Search' => 'Rechercher',
|
||||
'Nothing found.' => 'Rien trouvé.',
|
||||
'Search in the project "%s"' => 'Rechercher dans le projet « %s »',
|
||||
'Due date' => 'Date d\'échéance',
|
||||
'Others formats accepted: %s and %s' => 'Autres formats acceptés : %s et %s',
|
||||
'Description' => 'Description',
|
||||
'%d comments' => '%d commentaires',
|
||||
'%d comment' => '%d commentaire',
|
||||
'Email address invalid' => 'Adresse email invalide',
|
||||
'Your Google Account is not linked anymore to your profile.' => 'Votre compte Google n\'est plus relié à votre profile.',
|
||||
'Unable to unlink your Google Account.' => 'Impossible de supprimer votre compte Google.',
|
||||
'Google authentication failed' => 'Authentification Google échouée',
|
||||
'Unable to link your Google Account.' => 'Impossible de lier votre compte Google.',
|
||||
'Your Google Account is linked to your profile successfully.' => 'Votre compte Google est désormais lié à votre profile.',
|
||||
'Email' => 'Email',
|
||||
'Link my Google Account' => 'Lier mon compte Google',
|
||||
'Unlink my Google Account' => 'Ne plus utiliser mon compte Google',
|
||||
'Login with my Google Account' => 'Se connecter avec mon compte Google',
|
||||
'Project not found.' => 'Projet introuvable.',
|
||||
'Task #%d' => 'Tâche n°%d',
|
||||
'Task removed successfully.' => 'Tâche supprimée avec succès.',
|
||||
'Unable to remove this task.' => 'Impossible de supprimer cette tâche.',
|
||||
'Remove a task' => 'Supprimer une tâche',
|
||||
'Do you really want to remove this task: "%s"?' => 'Voulez-vous vraiment supprimer cette tâche « %s » ?',
|
||||
'Assign automatically a color based on a category' => 'Assigner automatiquement une couleur par rapport à une catégorie définie',
|
||||
'Assign automatically a category based on a color' => 'Assigner automatiquement une catégorie par rapport à une couleur définie',
|
||||
'Task creation or modification' => 'Création ou modification d\'une tâche',
|
||||
'Category' => 'Catégorie',
|
||||
'Category:' => 'Catégorie :',
|
||||
'Categories' => 'Catégories',
|
||||
'Category not found.' => 'Catégorie introuvable',
|
||||
'Your category have been created successfully.' => 'Votre catégorie a été créé avec succès.',
|
||||
'Unable to create your category.' => 'Impossible de créer votre catégorie.',
|
||||
'Your category have been updated successfully.' => 'Votre catégorie a été mise à jour avec succès.',
|
||||
'Unable to update your category.' => 'Impossible de mettre à jour votre catégorie.',
|
||||
'Remove a category' => 'Supprimer une catégorie',
|
||||
'Category removed successfully.' => 'Catégorie supprimée avec succès.',
|
||||
'Unable to remove this category.' => 'Impossible de supprimer cette catégorie.',
|
||||
'Category modification for the project "%s"' => 'Modification d\'une catégorie pour le projet « %s »',
|
||||
'Category Name' => 'Nom de la catégorie',
|
||||
'Categories for the project "%s"' => 'Catégories du projet « %s »',
|
||||
'Add a new category' => 'Ajouter une nouvelle catégorie',
|
||||
'Do you really want to remove this category: "%s"?' => 'Voulez-vous vraiment supprimer cette catégorie « %s » ?',
|
||||
'Filter by category' => 'Filtrer par catégorie',
|
||||
'All categories' => 'Toutes les catégories',
|
||||
'No category' => 'Aucune catégorie',
|
||||
'The name is required' => 'Le nom est requis',
|
||||
'Remove a file' => 'Supprimer un fichier',
|
||||
'Unable to remove this file.' => 'Impossible de supprimer ce fichier.',
|
||||
'File removed successfully.' => 'Fichier supprimé avec succès.',
|
||||
'Attach a document' => 'Joindre un document',
|
||||
'Do you really want to remove this file: "%s"?' => 'Voulez-vous vraiment supprimer ce fichier « %s » ?',
|
||||
'open' => 'ouvrir',
|
||||
'Attachments' => 'Pièces-jointes',
|
||||
'Edit the task' => 'Modifier la tâche',
|
||||
'Edit the description' => 'Modifier la description',
|
||||
'Add a comment' => 'Ajouter un commentaire',
|
||||
'Edit a comment' => 'Modifier un commentaire',
|
||||
'Summary' => 'Résumé',
|
||||
'Time tracking' => 'Gestion du temps',
|
||||
'Estimate:' => 'Estimation :',
|
||||
'Spent:' => 'Passé :',
|
||||
'Do you really want to remove this sub-task?' => 'Voulez-vous vraiment supprimer cette sous-tâche ?',
|
||||
'Remaining:' => 'Restant :',
|
||||
'hours' => 'heures',
|
||||
'spent' => 'passé',
|
||||
'estimated' => 'estimé',
|
||||
'Sub-Tasks' => 'Sous-Tâches',
|
||||
'Add a sub-task' => 'Ajouter une sous-tâche',
|
||||
'Original Estimate' => 'Estimation originale',
|
||||
'Create another sub-task' => 'Créer une autre sous-tâche',
|
||||
'Time Spent' => 'Temps passé',
|
||||
'Edit a sub-task' => 'Modifier une sous-tâche',
|
||||
'Remove a sub-task' => 'Supprimer une sous-tâche',
|
||||
'The time must be a numeric value' => 'Le temps doit-être une valeur numérique',
|
||||
'Todo' => 'À faire',
|
||||
'In progress' => 'En cours',
|
||||
'Sub-task removed successfully.' => 'Sous-tâche supprimée avec succès.',
|
||||
'Unable to remove this sub-task.' => 'Impossible de supprimer cette sous-tâche.',
|
||||
'Sub-task updated successfully.' => 'Sous-tâche mise à jour avec succès.',
|
||||
'Unable to update your sub-task.' => 'Impossible de mettre à jour votre sous-tâche.',
|
||||
'Unable to create your sub-task.' => 'Impossible de créer votre sous-tâche.',
|
||||
'Sub-task added successfully.' => 'Sous-tâche ajouté avec succès.',
|
||||
'Maximum size: ' => 'Taille maximum : ',
|
||||
'Unable to upload the file.' => 'Impossible de transférer le fichier.',
|
||||
'Display another project' => 'Afficher un autre projet',
|
||||
'Your GitHub account was successfully linked to your profile.' => 'Votre compte Github est désormais lié avec votre profile.',
|
||||
'Unable to link your GitHub Account.' => 'Impossible de lier votre compte Github.',
|
||||
'GitHub authentication failed' => 'L\'authentification avec Github à échouée',
|
||||
'Your GitHub account is no longer linked to your profile.' => 'Votre compte Github n\'est plus relié avec votre profile.',
|
||||
'Unable to unlink your GitHub Account.' => 'Impossible de déconnecter votre compte Github.',
|
||||
'Login with my GitHub Account' => 'Se connecter avec mon compte Github',
|
||||
'Link my GitHub Account' => 'Lier mon compte Github',
|
||||
'Unlink my GitHub Account' => 'Ne plus utiliser mon compte Github',
|
||||
'Created by %s' => 'Créé par %s',
|
||||
'Last modified on %B %e, %G at %k:%M %p' => 'Modifié le %d/%m/%Y à %H:%M',
|
||||
);
|
390
sources/app/Locales/pl_PL/translations.php
Normal file
390
sources/app/Locales/pl_PL/translations.php
Normal file
|
@ -0,0 +1,390 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
'English' => 'angielski',
|
||||
'French' => 'francuski',
|
||||
'Polish' => 'polski',
|
||||
'Portuguese (Brazilian)' => 'Portugalski (brazylijski)',
|
||||
'Spanish' => 'Hiszpański',
|
||||
// 'German' => '',
|
||||
// 'Chinese (Simplified)' => '',
|
||||
'None' => 'Brak',
|
||||
'edit' => 'edytuj',
|
||||
'Edit' => 'Edytuj',
|
||||
'remove' => 'usuń',
|
||||
'Remove' => 'Usuń',
|
||||
'Update' => 'Aktualizuj',
|
||||
'Yes' => 'Tak',
|
||||
'No' => 'Nie',
|
||||
'cancel' => 'anuluj',
|
||||
'or' => 'lub',
|
||||
'Yellow' => 'Żółty',
|
||||
'Blue' => 'Niebieski',
|
||||
'Green' => 'Zielony',
|
||||
'Purple' => 'Fioletowy',
|
||||
'Red' => 'Czerwony',
|
||||
'Orange' => 'Pomarańczowy',
|
||||
'Grey' => 'Szary',
|
||||
'Save' => 'Zapisz',
|
||||
'Login' => 'Login',
|
||||
'Official website:' => 'Oficjalna strona:',
|
||||
'Unassigned' => 'Nieprzypisany',
|
||||
'View this task' => 'Zobacz zadanie',
|
||||
'Remove user' => 'Usuń użytkownika',
|
||||
'Do you really want to remove this user: "%s"?' => 'Na pewno chcesz usunąć użytkownika: "%s"?',
|
||||
'New user' => 'Nowy użytkownik',
|
||||
'All users' => 'Wszyscy użytkownicy',
|
||||
'Username' => 'Nazwa użytkownika',
|
||||
'Password' => 'Hasło',
|
||||
'Default Project' => 'Domyślny projekt',
|
||||
'Administrator' => 'Administrator',
|
||||
'Sign in' => 'Zaloguj',
|
||||
'Users' => 'Użytkownicy',
|
||||
'No user' => 'Brak użytkowników',
|
||||
'Forbidden' => 'Zabroniony',
|
||||
'Access Forbidden' => 'Dostęp zabroniony',
|
||||
'Only administrators can access to this page.' => 'Tylko administrator może wejść na tą stronę.',
|
||||
'Edit user' => 'Edytuj użytkownika',
|
||||
'Logout' => 'Wyloguj',
|
||||
'Bad username or password' => 'Zła nazwa uyżytkownika lub hasło',
|
||||
'users' => 'użytkownicy',
|
||||
'projects' => 'projekty',
|
||||
'Edit project' => 'Edytuj projekt',
|
||||
'Name' => 'Nazwa',
|
||||
'Activated' => 'Aktywny',
|
||||
'Projects' => 'Projekty',
|
||||
'No project' => 'Brak projektów',
|
||||
'Project' => 'Projekt',
|
||||
'Status' => 'Status',
|
||||
'Tasks' => 'Zadania',
|
||||
'Board' => 'Tablica',
|
||||
'Inactive' => 'Nieaktywny',
|
||||
'Active' => 'Aktywny',
|
||||
'Column %d' => 'Kolumna %d',
|
||||
'Add this column' => 'Dodaj kolumnę',
|
||||
'%d tasks on the board' => '%d zadań na tablicy',
|
||||
'%d tasks in total' => '%d wszystkich zadań',
|
||||
'Unable to update this board.' => 'Nie można zaktualizować tablicy.',
|
||||
'Edit board' => 'Edytuj tablicę',
|
||||
'Disable' => 'Wyłącz',
|
||||
'Enable' => 'Włącz',
|
||||
'New project' => 'Nowy projekt',
|
||||
'Do you really want to remove this project: "%s"?' => 'Na pewno chcesz usunąć projekt: "%s"?',
|
||||
'Remove project' => 'Usuń projekt',
|
||||
'Boards' => 'Tablice',
|
||||
'Edit the board for "%s"' => 'Edytuj tablię dla "%s"',
|
||||
'All projects' => 'Wszystkie projekty',
|
||||
'Change columns' => 'Zmień kolumny',
|
||||
'Add a new column' => 'Dodaj nową kolumnę',
|
||||
'Title' => 'Tytuł',
|
||||
'Add Column' => 'Dodaj kolumnę',
|
||||
'Project "%s"' => 'Projekt "%s"',
|
||||
'Nobody assigned' => 'Nikt nie przypisany',
|
||||
'Assigned to %s' => 'Przypisane do %s',
|
||||
'Remove a column' => 'Usuń kolumnę',
|
||||
'Remove a column from a board' => 'Usuń kolumnę z tablicy',
|
||||
'Unable to remove this column.' => 'Nie udało się usunąć kolumny.',
|
||||
'Do you really want to remove this column: "%s"?' => 'Na pewno chcesz usunąć kolumnę: "%s"?',
|
||||
'This action will REMOVE ALL TASKS associated to this column!' => 'Wszystkie zadania w kolumnie zostaną usunięte!',
|
||||
'Settings' => 'Ustawienia',
|
||||
'Application settings' => 'Ustawienia aplikacji',
|
||||
'Language' => 'Język',
|
||||
'Webhooks token:' => 'Token :',
|
||||
'More information' => 'Więcej informacji',
|
||||
'Database size:' => 'Rozmiar bazy danych :',
|
||||
'Download the database' => 'Pobierz bazę danych',
|
||||
'Optimize the database' => 'Optymalizuj bazę danych',
|
||||
'(VACUUM command)' => '(komenda VACUUM)',
|
||||
'(Gzip compressed Sqlite file)' => '(baza danych spakowana Gzip)',
|
||||
'User settings' => 'Ustawienia użytkownika',
|
||||
'My default project:' => 'Mój domyślny projekt:',
|
||||
'Close a task' => 'Zakończ zadanie',
|
||||
'Do you really want to close this task: "%s"?' => 'Na pewno chcesz zakończyć to zadanie: "%s"?',
|
||||
'Edit a task' => 'Edytuj zadanie',
|
||||
'Column' => 'Kolumna',
|
||||
'Color' => 'Kolor',
|
||||
'Assignee' => 'Odpowiedzialny',
|
||||
'Create another task' => 'Dodaj kolejne zadanie',
|
||||
'New task' => 'Nowe zadanie',
|
||||
'Open a task' => 'Otwórz zadanie',
|
||||
'Do you really want to open this task: "%s"?' => 'Na pewno chcesz otworzyć zadanie: "%s"?',
|
||||
'Back to the board' => 'Powrót do tablicy',
|
||||
'Created on %B %e, %G at %k:%M %p' => 'Utworzono dnia %e %B %G o %k:%M',
|
||||
'There is nobody assigned' => 'Nikt nie jest przypisany',
|
||||
'Column on the board:' => 'Kolumna na tablicy:',
|
||||
'Status is open' => 'Status otwarty',
|
||||
'Status is closed' => 'Status zamknięty',
|
||||
'Close this task' => 'Zamknij zadanie',
|
||||
'Open this task' => 'Otwórz zadanie',
|
||||
'There is no description.' => 'Brak opisu.',
|
||||
'Add a new task' => 'Dodaj zadanie',
|
||||
'The username is required' => 'Nazwa użytkownika jest wymagana',
|
||||
'The maximum length is %d characters' => 'Maksymalna długość wynosi %d znaków',
|
||||
'The minimum length is %d characters' => 'Minimalna długość wynosi %d znaków',
|
||||
'The password is required' => 'Hasło jest wymagane',
|
||||
'This value must be an integer' => 'Wartość musi być liczbą całkowitą',
|
||||
'The username must be unique' => 'Nazwa użytkownika musi być unikalna',
|
||||
'The username must be alphanumeric' => 'Nazwa użytkownika musi być alfanumeryczna',
|
||||
'The user id is required' => 'ID użytkownika jest wymagane',
|
||||
'Passwords don\'t match' => 'Hasła nie pasują do siebie',
|
||||
'The confirmation is required' => 'Wymagane jest potwierdzenie',
|
||||
'The column is required' => 'Kolumna jest wymagana',
|
||||
'The project is required' => 'Projekt jest wymagany',
|
||||
'The color is required' => 'Kolor jest wymagany',
|
||||
'The id is required' => 'ID jest wymagane',
|
||||
'The project id is required' => 'ID projektu jest wymagane',
|
||||
'The project name is required' => 'Nazwa projektu jest wymagana',
|
||||
'This project must be unique' => 'Projekt musi być unikalny',
|
||||
'The title is required' => 'Tutył jest wymagany',
|
||||
'The language is required' => 'Język jest wymagany',
|
||||
'There is no active project, the first step is to create a new project.' => 'Brak aktywnych projektów. Pierwszym krokiem jest utworzenie nowego projektu.',
|
||||
'Settings saved successfully.' => 'Ustawienia zapisane.',
|
||||
'Unable to save your settings.' => 'Nie udało się zapisać ustawień.',
|
||||
'Database optimization done.' => 'Optymalizacja bazy danych zakończona.',
|
||||
'Your project have been created successfully.' => 'Projekt został pomyślnie utworzony.',
|
||||
'Unable to create your project.' => 'Nie udało się stworzyć projektu.',
|
||||
'Project updated successfully.' => 'Projekt zaktualizowany.',
|
||||
'Unable to update this project.' => 'Nie można zaktualizować projektu.',
|
||||
'Unable to remove this project.' => 'Nie można usunąć projektu.',
|
||||
'Project removed successfully.' => 'Projekt usunięty.',
|
||||
'Project activated successfully.' => 'Projekt aktywowany.',
|
||||
'Unable to activate this project.' => 'Nie można aktywować projektu.',
|
||||
'Project disabled successfully.' => 'Projekt wyłączony.',
|
||||
'Unable to disable this project.' => 'Nie można wyłączyć projektu.',
|
||||
'Unable to open this task.' => 'Nie można otworzyć tego zadania.',
|
||||
'Task opened successfully.' => 'Zadanie otwarte.',
|
||||
'Unable to close this task.' => 'Nie można zamknąć tego zadania.',
|
||||
'Task closed successfully.' => 'Zadanie zamknięte.',
|
||||
'Unable to update your task.' => 'Nie można zaktualizować tego zadania.',
|
||||
'Task updated successfully.' => 'Zadanie zaktualizowane.',
|
||||
'Unable to create your task.' => 'Nie można dodać zadania.',
|
||||
'Task created successfully.' => 'Zadanie zostało utworzone.',
|
||||
'User created successfully.' => 'Użytkownik dodany',
|
||||
'Unable to create your user.' => 'Nie udało się dodać użytkownika.',
|
||||
'User updated successfully.' => 'Użytkownik zaktualizowany.',
|
||||
'Unable to update your user.' => 'Nie udało się zaktualizować użytkownika.',
|
||||
'User removed successfully.' => 'Użytkownik usunięty.',
|
||||
'Unable to remove this user.' => 'Nie udało się usunąć użytkownika.',
|
||||
'Board updated successfully.' => 'Tablica została zaktualizowana.',
|
||||
'Ready' => 'Gotowe',
|
||||
'Backlog' => 'Log',
|
||||
'Work in progress' => 'W trakcie',
|
||||
'Done' => 'Zakończone',
|
||||
'Application version:' => 'Wersja aplikacji:',
|
||||
'Completed on %B %e, %G at %k:%M %p' => 'Zakończono dnia %e %B %G o %k:%M',
|
||||
'%B %e, %G at %k:%M %p' => '%e %B %G o %k:%M',
|
||||
'Date created' => 'Data utworzenia',
|
||||
'Date completed' => 'Data zakończenia',
|
||||
'Id' => 'Ident',
|
||||
'No task' => 'Brak zadań',
|
||||
'Completed tasks' => 'Ukończone zadania',
|
||||
'List of projects' => 'Lista projektów',
|
||||
'Completed tasks for "%s"' => 'Zadania zakończone dla "%s"',
|
||||
'%d closed tasks' => '%d zamkniętych zadań',
|
||||
'no task for this project' => 'brak zadań dla tego projektu',
|
||||
'Public link' => 'Link publiczny',
|
||||
'There is no column in your project!' => 'Brak kolumny w Twoim projekcie',
|
||||
'Change assignee' => 'Zmień odpowiedzialną osobę',
|
||||
'Change assignee for the task "%s"' => 'Zmień odpowiedzialną osobę dla zadania "%s"',
|
||||
'Timezone' => 'Strefa czasowa',
|
||||
'Actions' => 'Akcje',
|
||||
'Confirmation' => 'Powtórzenie hasła',
|
||||
'Description' => 'Opis',
|
||||
'Sorry, I didn\'t found this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych',
|
||||
'Page not found' => 'Strona nie istnieje',
|
||||
'Story Points' => 'Poziom trudności',
|
||||
'limit' => 'limit',
|
||||
'Task limit' => 'Limit zadań',
|
||||
'This value must be greater than %d' => 'Wartość musi być większa niż %d',
|
||||
'Edit project access list' => 'Edycja list dostępu dla projektu',
|
||||
'Edit users access' => 'Edytuj dostęp',
|
||||
'Allow this user' => 'Dodaj użytkownika',
|
||||
'Project access list for "%s"' => 'Lista uprawnionych dla projektu "%s"',
|
||||
'Only those users have access to this project:' => 'Użytkownicy mający dostęp:',
|
||||
'Don\'t forget that administrators have access to everything.' => 'Pamiętaj: Administratorzy mają zawsze dostęp do wszystkiego!',
|
||||
'revoke' => 'odbierz dostęp',
|
||||
'List of authorized users' => 'Lista użytkowników mających dostęp',
|
||||
'User' => 'Użytkownik',
|
||||
'Everybody have access to this project.' => 'Każdy ma dostęp do tego projektu.',
|
||||
'You are not allowed to access to this project.' => 'Nie masz dostępu do tego projektu.',
|
||||
'Comments' => 'Komentarze',
|
||||
'Post comment' => 'Dodaj komentarz',
|
||||
'Write your text in Markdown' => 'Możesz użyć Markdown',
|
||||
'Leave a comment' => 'Zostaw komentarz',
|
||||
'Comment is required' => 'Komentarz jest wymagany',
|
||||
'Comment added successfully.' => 'Komentarz dodany',
|
||||
'Unable to create your comment.' => 'Nie udało się dodać komentarza',
|
||||
'The description is required' => 'Opis jest wymagany',
|
||||
'Edit this task' => 'Edytuj zadanie',
|
||||
'Due Date' => 'Termin',
|
||||
'm/d/Y' => 'd/m/Y', // Date format parsed with php
|
||||
'month/day/year' => 'dzień/miesiąc/rok', // Help shown to the user
|
||||
'Invalid date' => 'Błędna data',
|
||||
'Must be done before %B %e, %G' => 'Termin do %e %B %G',
|
||||
'%B %e, %G' => '%e %B %G',
|
||||
'Automatic actions' => 'Akcje automatyczne',
|
||||
'Your automatic action have been created successfully.' => 'Twoja akcja została dodana',
|
||||
'Unable to create your automatic action.' => 'Nie udało się utworzyć akcji',
|
||||
'Remove an action' => 'Usuń akcję',
|
||||
'Unable to remove this action.' => 'Nie można usunąć akcji',
|
||||
'Action removed successfully.' => 'Akcja usunięta',
|
||||
'Automatic actions for the project "%s"' => 'Akcje automatyczne dla projektu "%s"',
|
||||
'Defined actions' => 'Zdefiniowane akcje',
|
||||
'Event name' => 'Nazwa zdarzenia',
|
||||
'Action name' => 'Nazwa akcji',
|
||||
'Action parameters' => 'Parametry akcji',
|
||||
'Action' => 'Akcja',
|
||||
'Event' => 'Zdarzenie',
|
||||
'When the selected event occurs execute the corresponding action.' => 'Gdy następuje wybrane zdarzenie, uruchom odpowiednią akcję',
|
||||
'Next step' => 'Następny krok',
|
||||
'Define action parameters' => 'Zdefiniuj parametry akcji',
|
||||
'Save this action' => 'Zapisz akcję',
|
||||
'Do you really want to remove this action: "%s"?' => 'Na pewno chcesz usunąć akcję "%s"?',
|
||||
'Remove an automatic action' => 'Usuń akcję automatyczną',
|
||||
'Close the task' => 'Zamknij zadanie',
|
||||
'Assign the task to a specific user' => 'Przypisz zadanie do wybranego użytkownika',
|
||||
'Assign the task to the person who does the action' => 'Przypisz zadanie to osoby wykonującej akcję',
|
||||
'Duplicate the task to another project' => 'Kopiuj zadanie do innego projektu',
|
||||
'Move a task to another column' => 'Przeniesienie zadania do innej kolumny',
|
||||
'Move a task to another position in the same column' => 'Zmiania pozycji zadania w kolumnie',
|
||||
'Task modification' => 'Modyfikacja zadania',
|
||||
'Task creation' => 'Tworzenie zadania',
|
||||
'Open a closed task' => 'Otwarcie zamkniętego zadania',
|
||||
'Closing a task' => 'Zamknięcie zadania',
|
||||
'Assign a color to a specific user' => 'Przypisz kolor do wybranego użytkownika',
|
||||
'Add an action' => 'Nowa akcja',
|
||||
'Column title' => 'Tytuł kolumny',
|
||||
'Position' => 'Pozycja',
|
||||
'Move Up' => 'Przenieś wyżej',
|
||||
'Move Down' => 'Przenieś niżej',
|
||||
'Duplicate to another project' => 'Skopiuj do innego projektu',
|
||||
'Duplicate' => 'Utwórz kopię',
|
||||
'link' => 'link',
|
||||
'Update this comment' => 'Zapisz komentarz',
|
||||
'Comment updated successfully.' => 'Komentarz został zapisany.',
|
||||
'Unable to update your comment.' => 'Nie udało się zapisanie komentarza.',
|
||||
'Remove a comment' => 'Usuń komentarz',
|
||||
'Comment removed successfully.' => 'Komentarz został usunięty.',
|
||||
'Unable to remove this comment.' => 'Nie udało się usunąć komentarza.',
|
||||
'Do you really want to remove this comment?' => 'Czy na pewno usunąć ten komentarz?',
|
||||
'Only administrators or the creator of the comment can access to this page.' => 'Tylko administratorzy oraz autor komentarza ma dostęp do tej strony.',
|
||||
'Details' => 'Szczegóły',
|
||||
'Current password for the user "%s"' => 'Aktualne hasło dla użytkownika "%s"',
|
||||
'The current password is required' => 'Wymanage jest aktualne hasło',
|
||||
'Wrong password' => 'Błędne hasło',
|
||||
'Reset all tokens' => 'Zresetuj wszystkie tokeny',
|
||||
'All tokens have been regenerated.' => 'Wszystkie tokeny zostały zresetowane.',
|
||||
'Unknown' => 'Nieznany',
|
||||
'Last logins' => 'Ostatnie logowania',
|
||||
'Login date' => 'Data logowania',
|
||||
'Authentication method' => 'Sposób autentykacji',
|
||||
'IP address' => 'Adres IP',
|
||||
'User agent' => 'Przeglądarka',
|
||||
'Persistent connections' => 'Stałe połączenia',
|
||||
'No session' => 'Brak sesji',
|
||||
'Expiration date' => 'Data zakończenia',
|
||||
'Remember Me' => 'Pamiętaj mnie',
|
||||
'Creation date' => 'Data utworzenia',
|
||||
// 'Filter by user' => '',
|
||||
// 'Filter by due date' => ',
|
||||
// 'Everybody' => '',
|
||||
// 'Open' => '',
|
||||
// 'Closed' => '',
|
||||
// 'Search' => '',
|
||||
// 'Nothing found.' => '',
|
||||
// 'Search in the project "%s"' => '',
|
||||
// 'Due date' => '',
|
||||
// 'Others formats accepted: %s and %s' => '',
|
||||
// 'Description' => '',
|
||||
// '%d comments' => '',
|
||||
// '%d comment' => '',
|
||||
// 'Email address invalid' => '',
|
||||
// 'Your Google Account is not linked anymore to your profile.' => '',
|
||||
// 'Unable to unlink your Google Account.' => '',
|
||||
// 'Google authentication failed' => '',
|
||||
// 'Unable to link your Google Account.' => '',
|
||||
// 'Your Google Account is linked to your profile successfully.' => '',
|
||||
// 'Email' => '',
|
||||
// 'Link my Google Account' => '',
|
||||
// 'Unlink my Google Account' => '',
|
||||
// 'Login with my Google Account' => '',
|
||||
// 'Project not found.' => '',
|
||||
// 'Task #%d' => '',
|
||||
// 'Task removed successfully.' => '',
|
||||
// 'Unable to remove this task.' => '',
|
||||
// 'Remove a task' => '',
|
||||
// 'Do you really want to remove this task: "%s"?' => '',
|
||||
// 'Assign automatically a color based on a category' => '',
|
||||
// 'Assign automatically a category based on a color' => '',
|
||||
// 'Task creation or modification' => '',
|
||||
// 'Category' => '',
|
||||
// 'Category:' => '',
|
||||
// 'Categories' => '',
|
||||
// 'Category not found.' => '',
|
||||
// 'Your category have been created successfully.' => '',
|
||||
// 'Unable to create your category.' => '',
|
||||
// 'Your category have been updated successfully.' => '',
|
||||
// 'Unable to update your category.' => '',
|
||||
// 'Remove a category' => '',
|
||||
// 'Category removed successfully.' => '',
|
||||
// 'Unable to remove this category.' => '',
|
||||
// 'Category modification for the project "%s"' => '',
|
||||
// 'Category Name' => '',
|
||||
// 'Categories for the project "%s"' => '',
|
||||
// 'Add a new category' => '',
|
||||
// 'Do you really want to remove this category: "%s"?' => '',
|
||||
// 'Filter by category' => '',
|
||||
// 'All categories' => '',
|
||||
// 'No category' => '',
|
||||
// 'The name is required' => '',
|
||||
// 'Remove a file' => '',
|
||||
// 'Unable to remove this file.' => '',
|
||||
// 'File removed successfully.' => '',
|
||||
// 'Attach a document' => '',
|
||||
// 'Do you really want to remove this file: "%s"?' => '',
|
||||
// 'open' => '',
|
||||
// 'Attachments' => '',
|
||||
// 'Edit the task' => '',
|
||||
// 'Edit the description' => '',
|
||||
// 'Add a comment' => '',
|
||||
// 'Edit a comment' => '',
|
||||
// 'Summary' => '',
|
||||
// 'Time tracking' => '',
|
||||
// 'Estimate:' => '',
|
||||
// 'Spent:' => '',
|
||||
// 'Do you really want to remove this sub-task?' => '',
|
||||
// 'Remaining:' => '',
|
||||
// 'hours' => '',
|
||||
// 'spent' => '',
|
||||
// 'estimated' => '',
|
||||
// 'Sub-Tasks' => '',
|
||||
// 'Add a sub-task' => '',
|
||||
// 'Original Estimate' => '',
|
||||
// 'Create another sub-task' => '',
|
||||
// 'Time Spent' => '',
|
||||
// 'Edit a sub-task' => '',
|
||||
// 'Remove a sub-task' => '',
|
||||
// 'The time must be a numeric value' => '',
|
||||
// 'Todo' => '',
|
||||
// 'In progress' => '',
|
||||
// 'Done' => '',
|
||||
// 'Sub-task removed successfully.' => '',
|
||||
// 'Unable to remove this sub-task.' => '',
|
||||
// 'Sub-task updated successfully.' => '',
|
||||
// 'Unable to update your sub-task.' => '',
|
||||
// 'Unable to create your sub-task.' => '',
|
||||
// 'Sub-task added successfully.' => '',
|
||||
// 'Maximum size: ' => '',
|
||||
// 'Unable to upload the file.' => '',
|
||||
// 'Display another project' => '',
|
||||
// 'Your GitHub account was successfully linked to your profile.' => '',
|
||||
// 'Unable to link your GitHub Account.' => '',
|
||||
// 'GitHub authentication failed' => '',
|
||||
// 'Your GitHub account is no longer linked to your profile.' => '',
|
||||
// 'Unable to unlink your GitHub Account.' => '',
|
||||
// 'Login with my GitHub Account' => '',
|
||||
// 'Link my GitHub Account' => '',
|
||||
// 'Unlink my GitHub Account' => '',
|
||||
// 'Created by %s' => 'Créé par %s',
|
||||
// 'Last modified on %B %e, %G at %k:%M %p' => '',
|
||||
);
|
387
sources/app/Locales/pt_BR/translations.php
Normal file
387
sources/app/Locales/pt_BR/translations.php
Normal file
|
@ -0,0 +1,387 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
'English' => 'Inglês',
|
||||
'French' => 'Francês',
|
||||
'Polish' => 'Polonês',
|
||||
'Portuguese (Brazilian)' => 'Português (Brasil)',
|
||||
'Spanish' => 'Espanhol',
|
||||
// 'German' => '',
|
||||
// 'Chinese (Simplified)' => '',
|
||||
'None' => 'Nenhum',
|
||||
'edit' => 'editar',
|
||||
'Edit' => 'Editar',
|
||||
'remove' => 'apagar',
|
||||
'Remove' => 'Apagar',
|
||||
'Update' => 'Atualizar',
|
||||
'Yes' => 'Sim',
|
||||
'No' => 'Não',
|
||||
'cancel' => 'cancelar',
|
||||
'or' => 'ou',
|
||||
'Yellow' => 'Amarelo',
|
||||
'Blue' => 'Azul',
|
||||
'Green' => 'Verde',
|
||||
'Purple' => 'Violeta',
|
||||
'Red' => 'Vermelho',
|
||||
'Orange' => 'Laranja',
|
||||
'Grey' => 'Cinza',
|
||||
'Save' => 'Salvar',
|
||||
'Login' => 'Login',
|
||||
'Official website:' => 'Site web oficial :',
|
||||
'Unassigned' => 'Não Atribuída',
|
||||
'View this task' => 'Ver esta tarefa',
|
||||
'Remove user' => 'Remover usuário',
|
||||
'Do you really want to remove this user: "%s"?' => 'Quer realmente remover este usuário: "%s"?',
|
||||
'New user' => 'Novo usuário',
|
||||
'All users' => 'Todos os usuários',
|
||||
'Username' => 'Nome do usuário',
|
||||
'Password' => 'Senha',
|
||||
'Default Project' => 'Projeto default',
|
||||
'Administrator' => 'Administrador',
|
||||
'Sign in' => 'Logar',
|
||||
'Users' => 'Usuários',
|
||||
'No user' => 'Sem usuário',
|
||||
'Forbidden' => 'Proibido',
|
||||
'Access Forbidden' => 'Acesso negado',
|
||||
'Only administrators can access to this page.' => 'Somente administradores têm acesso a esta página.',
|
||||
'Edit user' => 'Editar usuário',
|
||||
'Logout' => 'Logout',
|
||||
'Bad username or password' => 'Usuário ou senha inválidos',
|
||||
'users' => 'usuários',
|
||||
'projects' => 'projetos',
|
||||
'Edit project' => 'Editar projeto',
|
||||
'Name' => 'Nome',
|
||||
'Activated' => 'Ativo',
|
||||
'Projects' => 'Projetos',
|
||||
'No project' => 'Nenhum projeto',
|
||||
'Project' => 'Projeto',
|
||||
'Status' => 'Status',
|
||||
'Tasks' => 'Tarefas',
|
||||
'Board' => 'Quadro',
|
||||
'Inactive' => 'Inativo',
|
||||
'Active' => 'Ativo',
|
||||
'Column %d' => 'Coluna %d',
|
||||
'Add this column' => 'Adicionar esta coluna',
|
||||
'%d tasks on the board' => '%d tarefas no quadro',
|
||||
'%d tasks in total' => '%d tarefas no total',
|
||||
'Unable to update this board.' => 'Impossível atualizar este quadro.',
|
||||
'Edit board' => 'Modificar quadro',
|
||||
'Disable' => 'Desativar',
|
||||
'Enable' => 'Ativar',
|
||||
'New project' => 'Novo projeto',
|
||||
'Do you really want to remove this project: "%s"?' => 'Quer realmente remover este projeto: "%s" ?',
|
||||
'Remove project' => 'Remover projeto',
|
||||
'Boards' => 'Quadros',
|
||||
'Edit the board for "%s"' => 'Editar o quadro para "%s"',
|
||||
'All projects' => 'Todos os projetos',
|
||||
'Change columns' => 'Modificar colunas',
|
||||
'Add a new column' => 'Adicionar uma nova coluna',
|
||||
'Title' => 'Título',
|
||||
'Add Column' => 'Adicionar coluna',
|
||||
'Project "%s"' => 'Projeto "%s"',
|
||||
'Nobody assigned' => 'Ninguém designado',
|
||||
'Assigned to %s' => 'Designado para %s',
|
||||
'Remove a column' => 'Remover uma coluna',
|
||||
'Remove a column from a board' => 'Remover uma coluna do quadro',
|
||||
'Unable to remove this column.' => 'Impossível remover esta coluna.',
|
||||
'Do you really want to remove this column: "%s"?' => 'Quer realmente remover esta coluna: "%s"?',
|
||||
'This action will REMOVE ALL TASKS associated to this column!' => 'Esta ação vai REMOVER TODAS AS TAREFAS associadas a esta coluna!',
|
||||
'Settings' => 'Preferências',
|
||||
'Application settings' => 'Preferências da aplicação',
|
||||
'Language' => 'Idioma',
|
||||
'Webhooks token:' => 'Token de webhooks:',
|
||||
'More information' => 'Mais informação',
|
||||
'Database size:' => 'Tamanho do banco de dados:',
|
||||
'Download the database' => 'Download do banco de dados',
|
||||
'Optimize the database' => 'Otimizar o banco de dados',
|
||||
'(VACUUM command)' => '(Comando VACUUM)',
|
||||
'(Gzip compressed Sqlite file)' => '(Arquivo Sqlite comprimido com Gzip)',
|
||||
'User settings' => 'Configurações do usuário',
|
||||
'My default project:' => 'Meu projeto default:',
|
||||
'Close a task' => 'Encerrar uma tarefa',
|
||||
'Do you really want to close this task: "%s"?' => 'Quer realmente encerrar esta tarefa: "%s"?',
|
||||
'Edit a task' => 'Editar uma tarefa',
|
||||
'Column' => 'Coluna',
|
||||
'Color' => 'Cor',
|
||||
'Assignee' => 'Designação',
|
||||
'Create another task' => 'Criar uma outra tarefa (aproveitando os dados preenchidos)',
|
||||
'New task' => 'Nova tarefa',
|
||||
'Open a task' => 'Abrir uma tarefa',
|
||||
'Do you really want to open this task: "%s"?' => 'Quer realmente abrir esta tarefa: "%s"?',
|
||||
'Back to the board' => 'Voltar ao quadro',
|
||||
'Created on %B %e, %G at %k:%M %p' => 'Criado em %d %B %G às %H:%M',
|
||||
'There is nobody assigned' => 'Não há ninguém designado',
|
||||
'Column on the board:' => 'Coluna no quadro:',
|
||||
'Status is open' => 'Status está aberto',
|
||||
'Status is closed' => 'Status está fechado',
|
||||
'Close this task' => 'Fechar esta tarefa',
|
||||
'Open this task' => 'Abrir esta tarefa',
|
||||
'There is no description.' => 'Não há descrição.',
|
||||
'Add a new task' => 'Adicionar uma nova tarefa',
|
||||
'The username is required' => 'O nome de usuário é obrigatório',
|
||||
'The maximum length is %d characters' => 'O tamanho máximo são %d caracteres',
|
||||
'The minimum length is %d characters' => 'O tamanho mínimo são %d caracteres',
|
||||
'The password is required' => 'A senha é obrigatória',
|
||||
'This value must be an integer' => 'O valor deve ser um inteiro',
|
||||
'The username must be unique' => 'O nome de usuário deve ser único',
|
||||
'The username must be alphanumeric' => 'O nome de usuário deve ser alfanumérico, sem espaços ou _',
|
||||
'The user id is required' => 'O id de usuário é obrigatório',
|
||||
'Passwords don\'t match' => 'As senhas não conferem',
|
||||
'The confirmation is required' => 'A confirmação é obrigatória',
|
||||
'The column is required' => 'A coluna é obrigatória',
|
||||
'The project is required' => 'O projeto é obrigatório',
|
||||
'The color is required' => 'A cor é obrigatória',
|
||||
'The id is required' => 'O id é obrigatório',
|
||||
'The project id is required' => 'O id do projeto é obrigatório',
|
||||
'The project name is required' => 'O nome do projeto é obrigatório',
|
||||
'This project must be unique' => 'Este projeto deve ser único',
|
||||
'The title is required' => 'O título é obrigatório',
|
||||
'The language is required' => 'O idioma é obrigatório',
|
||||
'There is no active project, the first step is to create a new project.' => 'Não há projeto ativo. O primeiro passo é criar um novo projeto.',
|
||||
'Settings saved successfully.' => 'Configurações salvas com sucesso.',
|
||||
'Unable to save your settings.' => 'Impossível salvar suas configurações.',
|
||||
'Database optimization done.' => 'Otimização do banco de dados terminada.',
|
||||
'Your project have been created successfully.' => 'Seu projeto foi criado com sucesso.',
|
||||
'Unable to create your project.' => 'Impossível criar seu projeto.',
|
||||
'Project updated successfully.' => 'Projeto atualizado com sucesso.',
|
||||
'Unable to update this project.' => 'Impossível atualizar este projeto.',
|
||||
'Unable to remove this project.' => 'Impossível remover este projeto.',
|
||||
'Project removed successfully.' => 'Projeto removido com sucesso.',
|
||||
'Project activated successfully.' => 'Projeto ativado com sucesso.',
|
||||
'Unable to activate this project.' => 'Impossível ativar este projeto.',
|
||||
'Project disabled successfully.' => 'Projeto desabilitado com sucesso.',
|
||||
'Unable to disable this project.' => 'Impossível desabilitar este projeto.',
|
||||
'Unable to open this task.' => 'Impossível abrir esta tarefa.',
|
||||
'Task opened successfully.' => 'Tarefa aberta com sucesso.',
|
||||
'Unable to close this task.' => 'Impossível encerrar esta tarefa.',
|
||||
'Task closed successfully.' => 'Tarefa encerrada com sucesso.',
|
||||
'Unable to update your task.' => 'Impossível atualizar sua tarefa.',
|
||||
'Task updated successfully.' => 'Tarefa atualizada com sucesso.',
|
||||
'Unable to create your task.' => 'Impossível criar sua tarefa.',
|
||||
'Task created successfully.' => 'Tarefa criada com sucesso.',
|
||||
'User created successfully.' => 'Usuário criado com sucesso.',
|
||||
'Unable to create your user.' => 'Impossível criar seu usuário.',
|
||||
'User updated successfully.' => 'Usuário atualizado com sucesso.',
|
||||
'Unable to update your user.' => 'Impossível atualizar seu usuário.',
|
||||
'User removed successfully.' => 'Usuário removido com sucesso.',
|
||||
'Unable to remove this user.' => 'Impossível remover este usuário.',
|
||||
'Board updated successfully.' => 'Quadro atualizado com sucesso.',
|
||||
'Ready' => 'Pronto',
|
||||
'Backlog' => 'Backlog',
|
||||
'Work in progress' => 'Em andamento',
|
||||
'Done' => 'Encerrado',
|
||||
'Application version:' => 'Versão da aplicação:',
|
||||
'Completed on %B %e, %G at %k:%M %p' => 'Encerrado em %d %B %G às %H:%M',
|
||||
'%B %e, %G at %k:%M %p' => '%d %B %G às %H:%M',
|
||||
'Date created' => 'Data de criação',
|
||||
'Date completed' => 'Data de encerramento',
|
||||
'Id' => 'Id',
|
||||
'No task' => 'Nenhuma tarefa',
|
||||
'Completed tasks' => 'tarefas completadas',
|
||||
'List of projects' => 'Lista de projetos',
|
||||
'Completed tasks for "%s"' => 'Tarefas completadas por "%s"',
|
||||
'%d closed tasks' => '%d tarefas encerradas',
|
||||
'no task for this project' => 'nenhuma tarefa para este projeto',
|
||||
'Public link' => 'Link público',
|
||||
'There is no column in your project!' => 'Não há colunas no seu projeto!',
|
||||
'Change assignee' => 'Mudar a designação',
|
||||
'Change assignee for the task "%s"' => 'Modificar designação para a tarefa "%s"',
|
||||
'Timezone' => 'Fuso horário',
|
||||
'Sorry, I didn\'t found this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!',
|
||||
'Page not found' => 'Página não encontrada',
|
||||
'Story Points' => 'Complexidade',
|
||||
'limit' => 'limite',
|
||||
'Task limit' => 'Limite da tarefa',
|
||||
'This value must be greater than %d' => 'Este valor deve ser maior que %d',
|
||||
'Edit project access list' => 'Editar lista de acesso ao projeto', // new translations to brazilian portuguese starts here
|
||||
'Edit users access' => 'Editar acesso de usuários',
|
||||
'Allow this user' => 'Permitir esse usuário',
|
||||
'Project access list for "%s"' => 'Lista de acesso ao projeto para "%s"',
|
||||
'Only those users have access to this project:' => 'Somente estes usuários têm acesso a este projeto:',
|
||||
'Don\'t forget that administrators have access to everything.' => 'Não esqueça que administradores têm acesso a tudo.',
|
||||
'revoke' => 'revogar',
|
||||
'List of authorized users' => 'Lista de usuários autorizados',
|
||||
'User' => 'Usuário',
|
||||
'Everybody have access to this project.' => 'Todos têm acesso a este projeto.',
|
||||
'You are not allowed to access to this project.' => 'Você não está autorizado a acessar este projeto.',
|
||||
'Comments' => 'Comentários',
|
||||
'Post comment' => 'Postar comentário',
|
||||
'Write your text in Markdown' => 'Escreva seu texto em Markdown',
|
||||
'Leave a comment' => 'Deixe um comentário',
|
||||
'Comment is required' => 'Comentário é obrigatório',
|
||||
'Leave a description' => 'Deixe uma descrição',
|
||||
'Comment added successfully.' => 'Cpmentário adicionado com sucesso.',
|
||||
'Unable to create your comment.' => 'Impossível criar seu comentário.',
|
||||
'The description is required' => 'A descrição é obrigatória',
|
||||
'Edit this task' => 'Editar esta tarefa',
|
||||
'Due Date' => 'Data de vencimento',
|
||||
'm/d/Y' => 'd/m/Y', // Date format parsed with php
|
||||
'month/day/year' => 'dia/mês/ano', // Help shown to the user
|
||||
'Invalid date' => 'Data inválida',
|
||||
'Must be done before %B %e, %G' => 'Deve ser feito antes de %d %B %G',
|
||||
'%B %e, %G' => '%d %B %G',
|
||||
'Automatic actions' => 'Ações automáticas',
|
||||
'Your automatic action have been created successfully.' => 'Sua ação automética foi criada com sucesso.',
|
||||
'Unable to create your automatic action.' => 'Impossível criar sua ação automática.',
|
||||
'Remove an action' => 'Remover uma ação',
|
||||
'Unable to remove this action.' => 'Impossível remover esta ação',
|
||||
'Action removed successfully.' => 'Ação removida com sucesso.',
|
||||
'Automatic actions for the project "%s"' => 'Ações automáticas para o projeto "%s"',
|
||||
'Defined actions' => 'Ações definidas',
|
||||
'Event name' => 'Nome do evento',
|
||||
'Action name' => 'Nome da ação',
|
||||
'Action parameters' => 'Parâmetros da ação',
|
||||
'Action' => 'Ação',
|
||||
'Event' => 'Evento',
|
||||
'When the selected event occurs execute the corresponding action.' => 'Quando o evento selecionado ocorrer, execute a ação correspondente',
|
||||
'Next step' => 'Próximo passo',
|
||||
'Define action parameters' => 'Definir parêmetros da ação',
|
||||
'Save this action' => 'Salvar esta ação',
|
||||
'Do you really want to remove this action: "%s"?' => 'Você quer realmente remover esta ação: "%s"?',
|
||||
'Remove an automatic action' => 'Remove uma ação automática',
|
||||
'Close the task' => 'Fechar tarefa',
|
||||
'Assign the task to a specific user' => 'Designar a tarefa para um usuário específico',
|
||||
'Assign the task to the person who does the action' => 'Designar a tarefa para a pessoa que executa a ação',
|
||||
'Duplicate the task to another project' => 'Duplicar a tarefa para um outro projeto',
|
||||
'Move a task to another column' => 'Mover a tarefa para outra coluna',
|
||||
'Move a task to another position in the same column' => 'Mover a tarefa para outra posição, na mesma coluna',
|
||||
'Task modification' => 'Modificação de tarefa',
|
||||
'Task creation' => 'Criação de tarefa',
|
||||
'Open a closed task' => 'Reabrir uma tarefa fechada',
|
||||
'Closing a task' => 'Fechando uma tarefa',
|
||||
'Assign a color to a specific user' => 'Designar uma cor para um usuário específico',
|
||||
// 'Column title' => '',
|
||||
// 'Position' => '',
|
||||
// 'Move Up' => '',
|
||||
// 'Move Down' => '',
|
||||
// 'Duplicate to another project' => '',
|
||||
// 'Duplicate' => '',
|
||||
// 'link' => '',
|
||||
// 'Update this comment' => '',
|
||||
// 'Comment updated successfully.' => '',
|
||||
// 'Unable to update your comment.' => '',
|
||||
// 'Remove a comment' => '',
|
||||
// 'Comment removed successfully.' => '',
|
||||
// 'Unable to remove this comment.' => '',
|
||||
// 'Do you really want to remove this comment?' => '',
|
||||
// 'Only administrators or the creator of the comment can access to this page.' => '',
|
||||
// 'Details' => '',
|
||||
// 'Current password for the user "%s"' => '',
|
||||
// 'The current password is required' => '',
|
||||
// 'Wrong password' => '',
|
||||
// 'Reset all tokens' => '',
|
||||
// 'All tokens have been regenerated.' => '',
|
||||
// 'Unknown' => '',
|
||||
// 'Last logins' => '',
|
||||
// 'Login date' => '',
|
||||
// 'Authentication method' => '',
|
||||
// 'IP address' => '',
|
||||
// 'User agent' => '',
|
||||
// 'Persistent connections' => '',
|
||||
// 'No session' => '',
|
||||
// 'Expiration date' => '',
|
||||
// 'Remember Me' => '',
|
||||
// 'Creation date' => '',
|
||||
// 'Filter by user' => '',
|
||||
// 'Filter by due date' => ',
|
||||
// 'Everybody' => '',
|
||||
// 'Open' => '',
|
||||
// 'Closed' => '',
|
||||
// 'Search' => '',
|
||||
// 'Nothing found.' => '',
|
||||
// 'Search in the project "%s"' => '',
|
||||
// 'Due date' => '',
|
||||
// 'Others formats accepted: %s and %s' => '',
|
||||
// 'Description' => '',
|
||||
// '%d comments' => '',
|
||||
// '%d comment' => '',
|
||||
// 'Email address invalid' => '',
|
||||
// 'Your Google Account is not linked anymore to your profile.' => '',
|
||||
// 'Unable to unlink your Google Account.' => '',
|
||||
// 'Google authentication failed' => '',
|
||||
// 'Unable to link your Google Account.' => '',
|
||||
// 'Your Google Account is linked to your profile successfully.' => '',
|
||||
// 'Email' => '',
|
||||
// 'Link my Google Account' => '',
|
||||
// 'Unlink my Google Account' => '',
|
||||
// 'Login with my Google Account' => '',
|
||||
// 'Project not found.' => '',
|
||||
// 'Task #%d' => '',
|
||||
// 'Task removed successfully.' => '',
|
||||
// 'Unable to remove this task.' => '',
|
||||
// 'Remove a task' => '',
|
||||
// 'Do you really want to remove this task: "%s"?' => '',
|
||||
// 'Assign automatically a color based on a category' => '',
|
||||
// 'Assign automatically a category based on a color' => '',
|
||||
// 'Task creation or modification' => '',
|
||||
// 'Category' => '',
|
||||
// 'Category:' => '',
|
||||
// 'Categories' => '',
|
||||
// 'Category not found.' => '',
|
||||
// 'Your category have been created successfully.' => '',
|
||||
// 'Unable to create your category.' => '',
|
||||
// 'Your category have been updated successfully.' => '',
|
||||
// 'Unable to update your category.' => '',
|
||||
// 'Remove a category' => '',
|
||||
// 'Category removed successfully.' => '',
|
||||
// 'Unable to remove this category.' => '',
|
||||
// 'Category modification for the project "%s"' => '',
|
||||
// 'Category Name' => '',
|
||||
// 'Categories for the project "%s"' => '',
|
||||
// 'Add a new category' => '',
|
||||
// 'Do you really want to remove this category: "%s"?' => '',
|
||||
// 'Filter by category' => '',
|
||||
// 'All categories' => '',
|
||||
// 'No category' => '',
|
||||
// 'The name is required' => '',
|
||||
// 'Remove a file' => '',
|
||||
// 'Unable to remove this file.' => '',
|
||||
// 'File removed successfully.' => '',
|
||||
// 'Attach a document' => '',
|
||||
// 'Do you really want to remove this file: "%s"?' => '',
|
||||
// 'open' => '',
|
||||
// 'Attachments' => '',
|
||||
// 'Edit the task' => '',
|
||||
// 'Edit the description' => '',
|
||||
// 'Add a comment' => '',
|
||||
// 'Edit a comment' => '',
|
||||
// 'Summary' => '',
|
||||
// 'Time tracking' => '',
|
||||
// 'Estimate:' => '',
|
||||
// 'Spent:' => '',
|
||||
// 'Do you really want to remove this sub-task?' => '',
|
||||
// 'Remaining:' => '',
|
||||
// 'hours' => '',
|
||||
// 'spent' => '',
|
||||
// 'estimated' => '',
|
||||
// 'Sub-Tasks' => '',
|
||||
// 'Add a sub-task' => '',
|
||||
// 'Original Estimate' => '',
|
||||
// 'Create another sub-task' => '',
|
||||
// 'Time Spent' => '',
|
||||
// 'Edit a sub-task' => '',
|
||||
// 'Remove a sub-task' => '',
|
||||
// 'The time must be a numeric value' => '',
|
||||
// 'Todo' => '',
|
||||
// 'In progress' => '',
|
||||
// 'Done' => '',
|
||||
// 'Sub-task removed successfully.' => '',
|
||||
// 'Unable to remove this sub-task.' => '',
|
||||
// 'Sub-task updated successfully.' => '',
|
||||
// 'Unable to update your sub-task.' => '',
|
||||
// 'Unable to create your sub-task.' => '',
|
||||
// 'Sub-task added successfully.' => '',
|
||||
// 'Maximum size: ' => '',
|
||||
// 'Unable to upload the file.' => '',
|
||||
// 'Display another project' => '',
|
||||
// 'Your GitHub account was successfully linked to your profile.' => '',
|
||||
// 'Unable to link your GitHub Account.' => '',
|
||||
// 'GitHub authentication failed' => '',
|
||||
// 'Your GitHub account is no longer linked to your profile.' => '',
|
||||
// 'Unable to unlink your GitHub Account.' => '',
|
||||
// 'Login with my GitHub Account' => '',
|
||||
// 'Link my GitHub Account' => '',
|
||||
// 'Unlink my GitHub Account' => '',
|
||||
// 'Created by %s' => 'Créé par %s',
|
||||
// 'Last modified on %B %e, %G at %k:%M %p' => '',
|
||||
);
|
389
sources/app/Locales/sv_SE/translations.php
Normal file
389
sources/app/Locales/sv_SE/translations.php
Normal file
|
@ -0,0 +1,389 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
'English' => 'Engelska',
|
||||
'French' => 'Franska',
|
||||
'Polish' => 'Polska',
|
||||
'Portuguese (Brazilian)' => 'Portugisiska (Brasilien)',
|
||||
'Spanish' => 'Spanska',
|
||||
'German' => 'Tyska',
|
||||
'Swedish' => 'Svenska',
|
||||
'None' => 'Ingen',
|
||||
'edit' => 'redigera',
|
||||
'Edit' => 'Redigera',
|
||||
'remove' => 'ta bort',
|
||||
'Remove' => 'Ta bort',
|
||||
'Update' => 'Uppdatera',
|
||||
'Yes' => 'Ja',
|
||||
'No' => 'Nej',
|
||||
'cancel' => 'avbryt',
|
||||
'or' => 'eller',
|
||||
'Yellow' => 'Gul',
|
||||
'Blue' => 'Blå',
|
||||
'Green' => 'Grön',
|
||||
'Purple' => 'Lila',
|
||||
'Red' => 'Röd',
|
||||
'Orange' => 'Orange',
|
||||
'Grey' => 'Grå',
|
||||
'Save' => 'Spara',
|
||||
'Login' => 'Login',
|
||||
'Official website:' => 'Officiell webbsida:',
|
||||
'Unassigned' => 'Ej tilldelad',
|
||||
'View this task' => 'Se denna uppgift',
|
||||
'Remove user' => 'Ta bort användare',
|
||||
'Do you really want to remove this user: "%s"?' => 'Vill du verkligen ta bort användaren: "%s"?',
|
||||
'New user' => 'Ny användare',
|
||||
'All users' => 'Alla användare',
|
||||
'Username' => 'Användarnamn',
|
||||
'Password' => 'Lösenord',
|
||||
'Default Project' => 'Standardprojekt',
|
||||
'Administrator' => 'Administratör',
|
||||
'Sign in' => 'Logga in',
|
||||
'Users' => 'Användare',
|
||||
'No user' => 'Ingen användare',
|
||||
'Forbidden' => 'Ej tillåten',
|
||||
'Access Forbidden' => 'Ej tillåten',
|
||||
'Only administrators can access to this page.' => 'Bara adminstratörer har tillgång till denna sida.',
|
||||
'Edit user' => 'Ändra användare',
|
||||
'Logout' => 'Logga ut',
|
||||
'Bad username or password' => 'Fel användarnamn eller lösenord',
|
||||
'users' => 'Användare',
|
||||
'projects' => 'projekt',
|
||||
'Edit project' => 'Ändra projekt',
|
||||
'Name' => 'Namn',
|
||||
'Activated' => 'Aktiverad',
|
||||
'Projects' => 'Projekt',
|
||||
'No project' => 'Inget projekt',
|
||||
'Project' => 'Tavlor för projekt och planering',
|
||||
'Status' => 'Status',
|
||||
'Tasks' => 'Uppgifter',
|
||||
'Board' => 'Tavla',
|
||||
'Actions' => 'Åtgärder',
|
||||
'Inactive' => 'Inaktiv',
|
||||
'Active' => 'Aktiv',
|
||||
'Column %d' => 'Kolumn %d',
|
||||
'Add this column' => 'Lägg till kolumnen',
|
||||
'%d tasks on the board' => '%d uppgifter på tavlan',
|
||||
'%d tasks in total' => '%d uppgifter totalt',
|
||||
'Unable to update this board.' => 'Kunde inte uppdatera tavlan',
|
||||
'Edit board' => 'Ändra tavlan',
|
||||
'Disable' => 'Inaktivera',
|
||||
'Enable' => 'Aktivera',
|
||||
'New project' => 'Nytt projekt',
|
||||
'Do you really want to remove this project: "%s"?' => 'Vill du verkligen ta bort projektet: "%s" ?',
|
||||
'Remove project' => 'Ta bort projekt',
|
||||
'Boards' => 'Tavlor',
|
||||
'Edit the board for "%s"' => 'Ändra tavlan för "%s"',
|
||||
'All projects' => 'Alla projekt',
|
||||
'Change columns' => 'Ändra kolumner',
|
||||
'Add a new column' => 'Lägg till ny kolumn',
|
||||
'Title' => 'Titel',
|
||||
'Add Column' => 'Lägg till kolumn',
|
||||
'Project "%s"' => 'Tavlan "%s" är aktiv',
|
||||
'Nobody assigned' => 'Ingen tilldelad',
|
||||
'Assigned to %s' => 'Tilldelad %s',
|
||||
'Remove a column' => 'Ta bort en kolumn',
|
||||
'Remove a column from a board' => 'Ta bort en kolumn från tavlan',
|
||||
'Unable to remove this column.' => 'Kunde inte ta bort kolumnen.',
|
||||
'Do you really want to remove this column: "%s"?' => 'Vill du verkligen ta bort kolumnen: "%s"?',
|
||||
'This action will REMOVE ALL TASKS associated to this column!' => 'Denna åtgärd kommer att TA BORT ALLA uppgifter kopplade till kolumnen!',
|
||||
'Settings' => 'Inställningar',
|
||||
'Application settings' => 'Applikationsinställningar',
|
||||
'Language' => 'Språk',
|
||||
'Webhooks token:' => 'Token för webhooks:',
|
||||
'More information' => 'Mer information',
|
||||
'Database size:' => 'Databasstorlek:',
|
||||
'Download the database' => 'Ladda ner databasen',
|
||||
'Optimize the database' => 'Optimera databasen',
|
||||
'(VACUUM command)' => '(Vacuum kommando)',
|
||||
'(Gzip compressed Sqlite file)' => '(Gzip komprimera Sqlite filen)',
|
||||
'User settings' => 'Användarinställningar',
|
||||
'My default project:' => 'Mitt standardprojekt:',
|
||||
'Close a task' => 'Stäng en uppgift',
|
||||
'Do you really want to close this task: "%s"?' => 'Vill du verkligen stänga uppgiften: "%s"?',
|
||||
'Edit a task' => 'Ändra en uppgift',
|
||||
'Column' => 'Kolumn',
|
||||
'Color' => 'Färg',
|
||||
'Assignee' => 'Uppdragsinnehavare',
|
||||
'Create another task' => 'Skapa ännu en uppgift',
|
||||
'New task' => 'Ny uppgift',
|
||||
'Open a task' => 'Öppna en uppgift',
|
||||
'Do you really want to open this task: "%s"?' => 'Vill du verkligen öppna denna uppgift: "%s"?',
|
||||
'Back to the board' => 'Tillbaka till tavlan',
|
||||
'Created on %B %e, %G at %k:%M %p' => 'Skapad %d %B %G kl %H:%M',
|
||||
'There is nobody assigned' => 'Det finns ingen tilldelad',
|
||||
'Column on the board:' => 'Kolumn på tavlan:',
|
||||
'Status is open' => 'Statusen är öppen',
|
||||
'Status is closed' => 'Statusen är stängd',
|
||||
'Close this task' => 'Stäng uppgiften',
|
||||
'Open this task' => 'Öppna uppgiften',
|
||||
'There is no description.' => 'Det finns ingen beskrivning.',
|
||||
'Add a new task' => 'Lägg till en ny uppgift',
|
||||
'The username is required' => 'Användarnamnet måste anges',
|
||||
'The maximum length is %d characters' => 'Max antal bokstäver %d',
|
||||
'The minimum length is %d characters' => 'Minst antal bokstäver %d',
|
||||
'The password is required' => 'Lösenordet måste anges.',
|
||||
'This value must be an integer' => 'Detta värde måste vara ett heltal.',
|
||||
'The username must be unique' => 'Användarnamnet måste vara unikt',
|
||||
'The username must be alphanumeric' => 'Användarnamnet måste vara alfanumeriskt',
|
||||
'The user id is required' => 'Användar-ID måste anges',
|
||||
'Passwords doesn\'t matches' => 'Fel lösenord',
|
||||
'The confirmation is required' => 'Bekräftelse behövs.',
|
||||
'The column is required' => 'Kolumnen måste anges',
|
||||
'The project is required' => 'Projektet måste anges',
|
||||
'The color is required' => 'Färgen måste anges',
|
||||
'The id is required' => 'Aktuellt ID måste anges',
|
||||
'The project id is required' => 'Projekt-ID måste anges',
|
||||
'The project name is required' => 'Ett projektnamn måste anges',
|
||||
'This project must be unique' => 'Detta projekt måste vara unikt',
|
||||
'The title is required' => 'En titel måste anges.',
|
||||
'The language is required' => 'Språket måste anges',
|
||||
'There is no active project, the first step is to create a new project.' => 'Inget projekt är aktiverat, första steget är att skapa ett nytt projekt',
|
||||
'Settings saved successfully.' => 'Inställningarna har sparats.',
|
||||
'Unable to save your settings.' => 'Kunde inte spara dina ändringar',
|
||||
'Database optimization done.' => 'Databasen har optimerats.',
|
||||
'Your project have been created successfully.' => 'Ditt projekt har skapats.',
|
||||
'Unable to create your project.' => 'Kunde inte skapa ditt projekt.',
|
||||
'Project updated successfully.' => 'Projektet har uppdaterats.',
|
||||
'Unable to update this project.' => 'Kunde inte uppdatera detta projekt.',
|
||||
'Unable to remove this project.' => 'Kunde inte ta bort detta projekt.',
|
||||
'Project removed successfully.' => 'Projektet har tagits bort.',
|
||||
'Project activated successfully.' => 'Projektet har aktiverats.',
|
||||
'Unable to activate this project.' => 'Kunde inte aktivera detta projekt.',
|
||||
'Project disabled successfully.' => 'Projektet har stängts.',
|
||||
'Unable to disable this project.' => 'Kunde inte stänga detta projekt.',
|
||||
'Unable to open this task.' => 'Kunde inte öppna denna uppgift.',
|
||||
'Task opened successfully.' => 'Uppgiften har öppnats.',
|
||||
'Unable to close this task.' => 'Kunde inte stänga denna uppgift.',
|
||||
'Task closed successfully.' => 'Uppgiften har stängts.',
|
||||
'Unable to update your task.' => 'Kunde inte uppdatera din uppgift.',
|
||||
'Task updated successfully.' => 'Uppgiften har uppdaterats.',
|
||||
'Unable to create your task.' => 'Kunde inte skapa din uppgift.',
|
||||
'Task created successfully.' => 'Uppgiften har skapats.',
|
||||
'User created successfully.' => 'Användaren har skapats.',
|
||||
'Unable to create your user.' => 'Kunde inte skapa din användare.',
|
||||
'User updated successfully.' => 'Användaren har updaterats.',
|
||||
'Unable to update your user.' => 'Kunde inte uppdatera din användare.',
|
||||
'User removed successfully.' => 'Användaren har tagits bort.',
|
||||
'Unable to remove this user.' => 'Kunde inte ta bort denna användare.',
|
||||
'Board updated successfully.' => 'Tavlan uppdaterad.',
|
||||
'Ready' => 'Denna månad',
|
||||
'Backlog' => 'Att göra',
|
||||
'Work in progress' => 'Pågående',
|
||||
'Done' => 'Klart',
|
||||
'Application version:' => 'Version:',
|
||||
'Completed on %B %e, %G at %k:%M %p' => 'Slutfört %d %B %G kl %H:%M',
|
||||
'%B %e, %G at %k:%M %p' => '%d %B %G kl %H:%M',
|
||||
'Date created' => 'Skapat datum',
|
||||
'Date completed' => 'Slutfört datum',
|
||||
'Id' => 'ID',
|
||||
'No task' => 'Ingen uppgift',
|
||||
'Completed tasks' => 'Slutförda uppgifter',
|
||||
'List of projects' => 'Lista med projekt',
|
||||
'Completed tasks for "%s"' => 'Slutföra uppgifter för "%s"',
|
||||
'%d closed tasks' => '%d stängda uppgifter',
|
||||
'no task for this project' => 'inga uppgifter i detta projekt',
|
||||
'Public link' => 'Publik länk',
|
||||
'There is no column in your project!' => 'Det saknas kolumner i ditt projekt!',
|
||||
'Change assignee' => 'Ändra uppdragsinnehavare',
|
||||
'Change assignee for the task "%s"' => 'Ändra uppdragsinnehavare för uppgiften "%s"',
|
||||
'Timezone' => 'Tidszon',
|
||||
'Sorry, I didn\'t found this information in my database!' => 'Informationen kunde inte hittas i databasen.',
|
||||
'Page not found' => 'Sidan hittas inte',
|
||||
'Story Points' => 'Ungefärligt antal timmar',
|
||||
'limit' => 'max',
|
||||
'Task limit' => 'Uppgiftsbegränsning',
|
||||
'This value must be greater than %d' => 'Värdet måste vara större än %d',
|
||||
'Edit project access list' => 'Ändra projektåtkomst lista',
|
||||
'Edit users access' => 'Användaråtkomst',
|
||||
'Allow this user' => 'Tillåt användare',
|
||||
'Project access list for "%s"' => 'Behörighetslista för "%s"',
|
||||
'Only those users have access to this project:' => 'Bara de användarna har tillgång till detta projekt.',
|
||||
'Don\'t forget that administrators have access to everything.' => 'Glöm inte att administratörerna har rätt att göra allt.',
|
||||
'revoke' => 'Dra tillbaka behörighet',
|
||||
'List of authorized users' => 'Lista med behöriga användare',
|
||||
'User' => 'Användare',
|
||||
'Everybody have access to this project.' => 'Alla har tillgång till detta projekt.',
|
||||
'You are not allowed to access to this project.' => 'Du har inte tillgång till detta projekt.',
|
||||
'%B %e, %G at %k:%M %p' => '%d %B %G kl %H:%M',
|
||||
'Comments' => 'Kommentarer',
|
||||
'Post comment' => 'Ladda upp kommentar',
|
||||
'Write your text in Markdown' => 'Exempelsyntax för text',
|
||||
'Leave a comment' => 'Lämna en kommentar',
|
||||
'Comment is required' => 'En kommentar måste lämnas',
|
||||
'Leave a description' => 'Lämna en beskrivning',
|
||||
'Comment added successfully.' => 'Kommentaren har lagts till.',
|
||||
'Unable to create your comment.' => 'Kommentaren kunde inte laddas upp.',
|
||||
'The description is required' => 'En beskrivning måste lämnas',
|
||||
'Edit this task' => 'Ändra denna uppgift',
|
||||
'Due Date' => 'Måldatum',
|
||||
'm/d/Y' => 'd/m/Y', // Date format parsed with php
|
||||
'month/day/year' => 'dag/månad/år', // Help shown to the user
|
||||
'Invalid date' => 'Ej tillåtet datum',
|
||||
'Must be done before %B %e, %G' => 'Måste vara klart innan %B %e, %G',
|
||||
'%B %e, %G' => '%d %B %G',
|
||||
'Automatic actions' => 'Automatiska åtgärder',
|
||||
'Your automatic action have been created successfully.' => 'Din automatiska åtgärd har skapats.',
|
||||
'Unable to create your automatic action.' => 'Kunde inte skapa din automatiska åtgärd.',
|
||||
'Remove an action' => 'Ta bort en åtgärd',
|
||||
'Unable to remove this action.' => 'Kunde inte ta bort denna åtgärd.',
|
||||
'Action removed successfully.' => 'Åtgärden har tagits bort.',
|
||||
'Automatic actions for the project "%s"' => 'Automatiska åtgärder för projektet "%s"',
|
||||
'Defined actions' => 'Definierade åtgärder',
|
||||
'Event name' => 'Händelsenamn',
|
||||
'Action name' => 'Åtgärdsnamn',
|
||||
'Action parameters' => 'Åtgärdsparametrar',
|
||||
'Action' => 'Åtgärd',
|
||||
'Event' => 'Händelse',
|
||||
'When the selected event occurs execute the corresponding action.' => 'När händelsen inträffar, kör inställd åtgärd.',
|
||||
'Next step' => 'Nästa steg',
|
||||
'Define action parameters' => 'Definiera upp händelseparametrar',
|
||||
'Save this action' => 'Spara denna åtgärd',
|
||||
'Do you really want to remove this action: "%s"?' => 'Vill du verkligen ta bort denna åtgärd: "%s"?',
|
||||
'Remove an automatic action' => 'Ta bort en automatiskt åtgärd',
|
||||
'Close the task' => 'Stäng uppgiften',
|
||||
'Assign the task to a specific user' => 'Tilldela uppgiften till en specifik användare',
|
||||
'Assign the task to the person who does the action' => 'Tilldela uppgiften till personen som skapar den',
|
||||
'Duplicate the task to another project' => 'Kopiera uppgiften till ett annat projekt',
|
||||
'Move a task to another column' => 'Flytta en uppgift till en annan kolumn',
|
||||
'Move a task to another position in the same column' => 'Flytta en uppgift till ett nytt läge i samma kolumn',
|
||||
'Task modification' => 'Ändra uppgift',
|
||||
'Task creation' => 'Skapa uppgift',
|
||||
'Open a closed task' => 'Öppna en stängd uppgift',
|
||||
'Closing a task' => 'Stänger en uppgift',
|
||||
'Assign a color to a specific user' => 'Tilldela en färg till en specifik användare',
|
||||
'Column title' => 'Kolumnens titel',
|
||||
'Position' => 'Position',
|
||||
'Move Up' => 'Flytta upp',
|
||||
'Move Down' => 'Flytta ned',
|
||||
'Kopiera till ett annat projekt' => '',
|
||||
'Duplicate' => 'Kopiera uppgiften',
|
||||
'link' => 'länk',
|
||||
'Update this comment' => 'Uppdatera kommentaren',
|
||||
'Comment updated successfully.' => 'Kommentaren har uppdaterats.',
|
||||
'Unable to update your comment.' => 'Kunde inte uppdatera din kommentar.',
|
||||
'Remove a comment' => 'Ta bort en kommentar',
|
||||
'Comment removed successfully.' => 'Kommentaren har tagits bort.',
|
||||
'Unable to remove this comment.' => 'Kunde inte ta bort denna kommentar.',
|
||||
'Do you really want to remove this comment?' => 'Är du säker på att du vill ta bort denna kommentar?',
|
||||
'Only administrators or the creator of the comment can access to this page.' => 'Bara administratörer eller skaparen av kommentaren har tillgång till denna sida.',
|
||||
'Details' => 'Detaljer',
|
||||
'Current password for the user "%s"' => 'Nuvarande lösenord för användaren %s"',
|
||||
'The current password is required' => 'Det nuvarande lösenordet måste anges',
|
||||
'Wrong password' => 'Fel lösenord',
|
||||
'Reset all tokens' => 'Återställ alla tokens',
|
||||
'All tokens have been regenerated.' => 'Alla tokens har degenererats.',
|
||||
'Unknown' => 'Okänd',
|
||||
'Last logins' => 'Senaste inloggningarna',
|
||||
'Login date' => 'Inloggningsdatum',
|
||||
'Authentication method' => 'Autentiseringsmetoder',
|
||||
'IP address' => 'IP-adress',
|
||||
'User agent' => 'Användaragent/webbläsare',
|
||||
'Persistent connections' => 'Beständiga anslutningar',
|
||||
'No session' => 'Ingen session',
|
||||
'Expiration date' => 'Förfallodatum',
|
||||
'Remember Me' => 'Kom ihåg mig',
|
||||
'Creation date' => 'Skapatdatum',
|
||||
'Filter by user' => 'Filtrera på användare',
|
||||
'Filter by due date' => 'Filtrera på slutdatum',
|
||||
'Everybody' => 'Alla',
|
||||
'Open' => 'Öppen',
|
||||
'Closed' => 'Stängd',
|
||||
'Search' => 'Sök',
|
||||
'Nothing found.' => 'Inget kunde hittas.',
|
||||
'Search in the project "%s"' => 'Sök i projektet "%s"',
|
||||
'Due date' => 'Måldatum',
|
||||
'Others formats accepted: %s and %s' => 'Andra format som accepteras: %s and %s',
|
||||
'Description' => 'Beskrivning',
|
||||
'%d comments' => '%d kommentarer',
|
||||
'%d comment' => '%d kommentar',
|
||||
'Email address invalid' => 'Epost-adressen ogiltig',
|
||||
'Your Google Account is not linked anymore to your profile.' => 'Ditt Google-konto är inte längre länkat till din profil',
|
||||
'Unable to unlink your Google Account.' => 'Kunde inte ta bort länken till ditt Google-konto.',
|
||||
'Google authentication failed' => 'Autentiseringen för Google misslyckades',
|
||||
'Unable to link your Google Account.' => 'Kunde inte länka till ditt Google-konto',
|
||||
'Your Google Account is linked to your profile successfully.' => 'Ditt Google-konto har kopplats till din profil.',
|
||||
'Email' => 'Epost',
|
||||
'Link my Google Account' => 'Länka till mitt Google-konto',
|
||||
'Unlink my Google Account' => 'Ta bort länken till mitt Google-konto',
|
||||
'Login with my Google Account' => 'Logga in med mitt Google-konto',
|
||||
'Project not found.' => 'Projektet kunde inte hittas',
|
||||
'Task #%d' => 'Uppgift #%d',
|
||||
'Task removed successfully.' => 'Uppgiften har tagits bort',
|
||||
'Unable to remove this task.' => 'Kunde inte ta bort denna uppgift',
|
||||
'Remove a task' => 'Ta bort en uppgift',
|
||||
'Do you really want to remove this task: "%s"?' => 'Vill du verkligen ta bort denna uppgift: "%s"?',
|
||||
'Assign automatically a color based on a category' => 'Tilldela automatiskt en färg baserad på en kategori',
|
||||
'Assign automatically a category based on a color' => 'Tilldela automatiskt en kategori baserad på en färg',
|
||||
'Task creation or modification' => 'Skapa eller ändra uppgift',
|
||||
'Category' => 'Kategori',
|
||||
'Category:' => 'Kategori:',
|
||||
'Categories' => 'Ange kategorier',
|
||||
'Category not found.' => 'Kategorin hittades inte',
|
||||
'Your category have been created successfully.' => 'Din kategori har skapats',
|
||||
'Unable to create your category.' => 'Kunde inte skapa din kategori',
|
||||
'Your category have been updated successfully.' => 'Din kategori har uppdaterats',
|
||||
'Unable to update your category.' => 'Kunde inte uppdatera din kategori',
|
||||
'Remove a category' => 'Ta bort en kategori',
|
||||
'Category removed successfully.' => 'Kategorin har tagits bort',
|
||||
'Unable to remove this category.' => 'Kunde inte ta bort denna kategori',
|
||||
'Category modification for the project "%s"' => 'Ändring av kategori för projektet "%s"',
|
||||
'Category Name' => 'Kategorinamn',
|
||||
'Categories for the project "%s"' => 'Kategorier för projektet "%s"',
|
||||
'Add a new category' => 'Lägg till en kategori',
|
||||
'Do you really want to remove this category: "%s"?' => 'Vill du verkligen ta bort denna kategori: "%s"?',
|
||||
'Filter by category' => 'Filtrera på kategori',
|
||||
'All categories' => 'Alla kategorier',
|
||||
'No category' => 'Ingen kategori',
|
||||
'The name is required' => 'Namnet måste anges',
|
||||
'Remove a file' => 'Ta bort en fil',
|
||||
'Unable to remove this file.' => 'Kunde inte ta bort denna fil',
|
||||
'File removed successfully.' => 'Filen har tagits bort',
|
||||
'Attach a document' => 'Bifoga ett dokument',
|
||||
'Do you really want to remove this file: "%s"?' => 'Vill du verkligen ta bort denna fil:"%s"?',
|
||||
'open' => 'öppen',
|
||||
'Attachments' => 'Bifogade filer',
|
||||
'Edit the task' => 'Ändra uppgiften',
|
||||
'Edit the description' => 'Ändra beskrivningen',
|
||||
'Add a comment' => 'Lägg till kommentar',
|
||||
'Edit a comment' => 'Ändra en kommentar',
|
||||
'Summary' => 'Sammanfattning',
|
||||
'Time tracking' => 'Tidsåtgång',
|
||||
'Estimate:' => 'Uppskattning',
|
||||
'Spent:' => 'Nedlagd tid',
|
||||
'Do you really want to remove this sub-task?' => 'Vill du verkligen ta bort deluppgiften?',
|
||||
'Remaining:' => 'Återstående:',
|
||||
'hours' => 'timmar',
|
||||
'spent' => 'nedlagt',
|
||||
'estimated' => 'uppskattat',
|
||||
'Sub-Tasks' => 'Deluppgifter',
|
||||
'Add a sub-task' => 'Lägg till deluppgift',
|
||||
'Original Estimate' => 'Ursprunglig uppskattning',
|
||||
'Create another sub-task' => 'Skapa en till deluppgift',
|
||||
'Time Spent' => 'Nedlagd tid',
|
||||
'Edit a sub-task' => 'Ändra en deluppgift',
|
||||
'Remove a sub-task' => 'Ta bort en deluppgift',
|
||||
'The time must be a numeric value' => 'Tiden måste ha ett numeriskt värde',
|
||||
'Todo' => 'Att göra',
|
||||
'In progress' => 'Pågående',
|
||||
'Done' => 'Slutfört',
|
||||
'Sub-task removed successfully.' => 'Deluppgiften har tagits bort.',
|
||||
'Unable to remove this sub-task.' => 'Kunde inte ta bort denna deluppgift.',
|
||||
'Sub-task updated successfully.' => 'Deluppgiften har uppdaterats.',
|
||||
'Unable to update your sub-task.' => 'Kunde inte uppdatera din deluppgift.',
|
||||
'Unable to create your sub-task.' => 'Kunde inte skapa din deluppgift.',
|
||||
'Sub-task added successfully.' => 'Deluppgiften har lagts till.',
|
||||
'Maximum size: ' => 'Maxstorlek: ',
|
||||
'Unable to upload the file.' => 'Kunde inte ladda upp filen.',
|
||||
'Display another project' => 'Visa ett annat projekt',
|
||||
'Your GitHub account was successfully linked to your profile.' => 'Ditt GitHub-konto har anslutits till din profil.',
|
||||
'Unable to link your GitHub Account.' => 'Kunde inte ansluta ditt GitHub-konto.',
|
||||
'GitHub authentication failed' => 'GitHub-verifiering misslyckades',
|
||||
'Your GitHub account is no longer linked to your profile.' => 'Ditt GitHub-konto är inte längre anslutet till din profil.',
|
||||
'Unable to unlink your GitHub Account.' => 'Kunde inte koppla ifrån ditt GitHub-konto.',
|
||||
'Login with my GitHub Account' => 'Logga in med mitt GitHub-konto',
|
||||
'Link my GitHub Account' => 'Anslut mitt GitHub-konto',
|
||||
'Unlink my GitHub Account' => 'Koppla ifrån mitt GitHub-konto',
|
||||
'Created by %s' => 'Skapad av %s',
|
||||
'Last modified on %B %e, %G at %k:%M %p' => 'Senaste ändring %B %e, %G kl %k:%M %p'',
|
||||
);
|
395
sources/app/Locales/zh_CN/translations.php
Normal file
395
sources/app/Locales/zh_CN/translations.php
Normal file
|
@ -0,0 +1,395 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
'English' => '英语',
|
||||
'French' => '法语',
|
||||
'Polish' => '波兰语',
|
||||
'Portuguese (Brazilian)' => '葡萄牙语 (巴西)',
|
||||
'Spanish' => '西班牙语',
|
||||
'German' => '德语',
|
||||
'Chinese (Simplified)' => '中文(简体)',
|
||||
'None' => '未知',
|
||||
'edit' => '修改',
|
||||
'Edit' => '修改',
|
||||
'remove' => '移除',
|
||||
'Remove' => '移除',
|
||||
'Update' => '更新',
|
||||
'Yes' => '是',
|
||||
'No' => '否',
|
||||
'cancel' => '取消',
|
||||
'or' => '或者',
|
||||
'Yellow' => '黄色',
|
||||
'Blue' => '蓝色',
|
||||
'Green' => '绿色',
|
||||
'Purple' => '紫色',
|
||||
'Red' => '红色',
|
||||
'Orange' => '橘色',
|
||||
'Grey' => '灰色',
|
||||
'Save' => '保存',
|
||||
'Login' => '登陆',
|
||||
'Official website:' => '官方网站:',
|
||||
'Unassigned' => '未指定',
|
||||
'View this task' => '查看该任务',
|
||||
'Add a sub-task' => '添加一个子任务',
|
||||
'Original Estimate' => '初步预计耗时',
|
||||
'hours' => '小时',
|
||||
'Create another sub-task' => '创建另一个子任务',
|
||||
'Remove user' => '移除用户',
|
||||
'Do you really want to remove this user: "%s"?' => '你确定要移除这个用户吗:"%s"?',
|
||||
'New user' => '新用户',
|
||||
'All users' => '所有用户',
|
||||
'Username' => '用户名',
|
||||
'Password' => '密码',
|
||||
'Default Project' => '默认项目',
|
||||
'Administrator' => '管理员',
|
||||
'Sign in' => '注册',
|
||||
'Users' => '用户组',
|
||||
'No user' => '没有用户',
|
||||
'Forbidden' => '禁止',
|
||||
'Access Forbidden' => '禁止进入',
|
||||
'Only administrators can access to this page.' => '只有管理员可以查看该页面。',
|
||||
'Edit user' => '修改用户',
|
||||
'Logout' => '登出',
|
||||
'Bad username or password' => '用户名或密码错误',
|
||||
'users' => '用户组',
|
||||
'projects' => '项目群',
|
||||
'Edit project' => '修改项目',
|
||||
'Name' => '名称',
|
||||
'Activated' => '已激活',
|
||||
'Projects' => '项目群',
|
||||
'No project' => '无项目',
|
||||
'Project' => '项目',
|
||||
'Status' => '状态',
|
||||
'Tasks' => '任务群',
|
||||
'Board' => '看板',
|
||||
'Inactive' => '未激活',
|
||||
'Active' => '激活',
|
||||
'Column %d' => '第%d栏目',
|
||||
'Add this column' => '加入该栏目',
|
||||
'%d tasks on the board' => '看板目前有%d个任务',
|
||||
'%d tasks in total' => '总共有%d个任务',
|
||||
'Unable to update this board.' => '无法更新该看板。',
|
||||
'Edit board' => '修改看板',
|
||||
'Disable' => '禁用',
|
||||
'Enable' => '启用',
|
||||
'New project' => '新项目',
|
||||
'Do you really want to remove this project: "%s"?' => '你确定要移除该项目吗:"%s"?',
|
||||
'Remove project' => '移除项目',
|
||||
'Boards' => '看板群',
|
||||
'Edit the board for "%s"' => '为"%s"修改看板',
|
||||
'All projects' => '所有项目',
|
||||
'Change columns' => '更改栏目',
|
||||
'Add a new column' => '添加新栏目',
|
||||
'Title' => '标题',
|
||||
'Add Column' => '添加栏目',
|
||||
'Project "%s"' => '项目 "%s"',
|
||||
'Nobody assigned' => '无人被指派',
|
||||
'Assigned to %s' => '指派给 %s',
|
||||
'Remove a column' => '移除一个栏目',
|
||||
'Remove a column from a board' => '从看板移除一个栏目',
|
||||
'Unable to remove this column.' => '无法移除该栏目。',
|
||||
'Do you really want to remove this column: "%s"?' => '你确定要移除该栏目:"%s"吗?',
|
||||
'This action will REMOVE ALL TASKS associated to this column!' => '该行为将移除与该栏目相关的所有项目!',
|
||||
'Settings' => '设置',
|
||||
'Application settings' => '应用设置',
|
||||
'Language' => '语言',
|
||||
'Webhooks token:' => '页面钩子令牌:',
|
||||
'More information' => '更多信息',
|
||||
'Database size:' => '数据库大小:',
|
||||
'Download the database' => '下载数据库',
|
||||
'Optimize the database' => '优化数据库',
|
||||
'(VACUUM command)' => '(数据库归整指令)',
|
||||
'(Gzip compressed Sqlite file)' => '(用Gzip压缩Sqlite文件)',
|
||||
'User settings' => '用户设置',
|
||||
'My default project:' => '我的默认项目:',
|
||||
'Close a task' => '关闭一个项目',
|
||||
'Do you really want to close this task: "%s"?' => '你确定要关闭该项目?"%s"',
|
||||
'Edit a task' => '修改一个项目',
|
||||
'Column' => '栏目',
|
||||
'Color' => '颜色',
|
||||
'Assignee' => '负责人',
|
||||
'Create another task' => '创建另一个项目',
|
||||
'New task' => '新项目',
|
||||
'Open a task' => '开一个项目',
|
||||
'Do you really want to open this task: "%s"?' => '你确定要开这个项目吗?"%s"',
|
||||
'Back to the board' => '回到看板',
|
||||
'Created on %B %e, %G at %k:%M %p' => '在%d/%m/%Y %H:%M创建',
|
||||
'There is nobody assigned' => '无人负责',
|
||||
'Column on the board:' => '看板上的栏目:',
|
||||
'Status is open' => '开放状态',
|
||||
'Status is closed' => '关闭状态',
|
||||
'Close this task' => '关闭该项目',
|
||||
'Open this task' => '开放该项目',
|
||||
'There is no description.' => '无描述。',
|
||||
'Add a new task' => '添加新任务',
|
||||
'The username is required' => '需要用户名',
|
||||
'The maximum length is %d characters' => '最长%d个英文字符',
|
||||
'The minimum length is %d characters' => '最短%d个英文自负',
|
||||
'The password is required' => '需要密码',
|
||||
'This value must be an integer' => '该值必须为整数',
|
||||
'The username must be unique' => '用户名必须唯一',
|
||||
'The username must be alphanumeric' => '用户名必须是英文字符或数字组成',
|
||||
'The user id is required' => '用户id是必须的',
|
||||
'Passwords doesn\'t matches' => '密码不匹配',
|
||||
'The confirmation is required' => '需要确认',
|
||||
'The column is required' => '需要指定栏目',
|
||||
'The project is required' => '需要指定项目',
|
||||
'The color is required' => '需要指定颜色',
|
||||
'The id is required' => '需要指定id',
|
||||
'The project id is required' => '需要指定项目id',
|
||||
'The project name is required' => '需要指定项目名称',
|
||||
'This project must be unique' => '项目名称必须唯一',
|
||||
'The title is required' => '需要指定标题',
|
||||
'The language is required' => '需要指定语言',
|
||||
'There is no active project, the first step is to create a new project.' => '尚无活跃项目,请首先创建一个新项目。',
|
||||
'Settings saved successfully.' => '设置成功保存。',
|
||||
'Unable to save your settings.' => '无法保存你的设置。',
|
||||
'Database optimization done.' => '数据库优化完成。',
|
||||
'Your project have been created successfully.' => '您的项目已经成功创建。',
|
||||
'Unable to create your project.' => '无法为您创建项目。',
|
||||
'Project updated successfully.' => '项目成功更新。',
|
||||
'Unable to update this project.' => '无法更新该项目。',
|
||||
'Unable to remove this project.' => '无法移除该项目。',
|
||||
'Project removed successfully.' => '项目成功移除。',
|
||||
'Project activated successfully.' => '项目成功激活。',
|
||||
'Unable to activate this project.' => '无法激活该项目。',
|
||||
'Project disabled successfully.' => '项目成功禁用。',
|
||||
'Unable to disable this project.' => '无法禁用该项目。',
|
||||
'Unable to open this task.' => '无法开启该任务。',
|
||||
'Task opened successfully.' => '任务开启成功。',
|
||||
'Unable to close this task.' => '无法关闭该任务。',
|
||||
'Task closed successfully.' => '任务成功关闭。',
|
||||
'Unable to update your task.' => '无法更新您的任务。',
|
||||
'Task updated successfully.' => '任务成功更新。',
|
||||
'Unable to create your task.' => '无法为您创建项目。',
|
||||
'Task created successfully.' => '任务成功创建。',
|
||||
'User created successfully.' => '用户成功创建。',
|
||||
'Unable to create your user.' => '无法创建用户。',
|
||||
'User updated successfully.' => '用户成功更新。',
|
||||
'Unable to update your user.' => '无法为您更新用户。',
|
||||
'User removed successfully.' => '用户成功移除。',
|
||||
'Unable to remove this user.' => '无法移除该用户。',
|
||||
'Board updated successfully.' => '看板成功更新。',
|
||||
'Ready' => '预备',
|
||||
'Backlog' => '积压',
|
||||
'Work in progress' => '工作进行中',
|
||||
'Done' => '完成',
|
||||
'Application version:' => '应用程序版本',
|
||||
'Completed on %B %e, %G at %k:%M %p' => '于%Y年%m月%d日%H时%M分完成',
|
||||
'%B %e, %G at %k:%M %p' => '%Y年%m月%d日%H时%M分',
|
||||
'Date created' => '创建时间',
|
||||
'Date completed' => '完成时间',
|
||||
'Id' => '昵称',
|
||||
'No task' => '无任务',
|
||||
'Completed tasks' => '已完成任务',
|
||||
'List of projects' => '项目列表',
|
||||
'Completed tasks for "%s"' => '任务因"%s"原因完成',
|
||||
'%d closed tasks' => '%d个已关闭任务',
|
||||
'no task for this project' => '该项目尚无任务',
|
||||
'Public link' => '公开链接',
|
||||
'There is no column in your project!' => '该项目尚无栏目项!',
|
||||
'Change assignee' => '被指派人变更',
|
||||
'Change assignee for the task "%s"' => '为任务"%s"更改被指派人',
|
||||
'Timezone' => '时区',
|
||||
'Sorry, I didn\'t found this information in my database!' => '抱歉,无法在数据库中找到该信息!',
|
||||
'Page not found' => '页面未找到',
|
||||
'Story Points' => '评估分值',
|
||||
'limit' => '限制',
|
||||
'Task limit' => '任务限制',
|
||||
'This value must be greater than %d' => '该数值必须大于%d',
|
||||
'Edit project access list' => '编辑项目存取列表',
|
||||
'Edit users access' => '编辑用户存取权限',
|
||||
'Allow this user' => '允许该用户',
|
||||
'Project access list for "%s"' => '"%s"的项目存取列表',
|
||||
'Only those users have access to this project:' => '只有这些用户有该项目的存取权限:',
|
||||
'Don\'t forget that administrators have access to everything.' => '别忘了管理员有一切的权限。',
|
||||
'revoke' => '撤销',
|
||||
'List of authorized users' => '已授权的用户列表',
|
||||
'User' => '用户',
|
||||
'Everybody have access to this project.' => '任何人都有该项目权限。',
|
||||
'You are not allowed to access to this project.' => '您对该项目没有权限。',
|
||||
'Comments' => '评论',
|
||||
'Post comment' => '发表评论',
|
||||
'Write your text in Markdown' => '用Markdown格式编写',
|
||||
'Leave a comment' => '留言',
|
||||
'Comment is required' => '必须得有评论',
|
||||
'Leave a description' => '给一个描述',
|
||||
'Comment added successfully.' => '评论成功添加。',
|
||||
'Unable to create your comment.' => '无法创建评论。',
|
||||
'The description is required' => '必须得有描述',
|
||||
'Edit this task' => '编辑该任务',
|
||||
'Due Date' => '到期',
|
||||
'm/d/Y' => 'Y/m/d', // Date format parsed with php
|
||||
'month/day/year' => '年/月/日', // Help shown to the user
|
||||
'Invalid date' => '无效日期',
|
||||
'Must be done before %B %e, %G' => '必须在%Y年%m月%d日前完成',
|
||||
'%B %e, %G' => '%Y/%m/%d',
|
||||
'Automatic actions' => '自动行为',
|
||||
'Add an action' => '添加一个行为',
|
||||
'Assign automatically a color based on a category' => '基于一个分类自动指派颜色',
|
||||
'Assign automatically a category based on a color' => '基于一种颜色自动指派分类',
|
||||
'Your automatic action have been created successfully.' => '您的自动行为已成功创建',
|
||||
'Unable to create your automatic action.' => '无法为您创建自动行为。',
|
||||
'Remove an action' => '移除一个行为。',
|
||||
'Unable to remove this action.' => '无法移除该行为',
|
||||
'Action removed successfully.' => '成功移除行为。',
|
||||
'Automatic actions for the project "%s"' => '项目"%s"的自动行为',
|
||||
'Defined actions' => '已定义的行为',
|
||||
'Event name' => '事件名称',
|
||||
'Action name' => '行为名称',
|
||||
'Action parameters' => '行为参数',
|
||||
'Action' => '行为',
|
||||
'Actions' => '行为',
|
||||
'Event' => '事件',
|
||||
'When the selected event occurs execute the corresponding action.' => '当所选事件发生时执行相应行为。',
|
||||
'Next step' => '下一步',
|
||||
'Define action parameters' => '定义行为参数',
|
||||
'Save this action' => '保存该行为',
|
||||
'Do you really want to remove this action: "%s"?' => '确定要益处该行为"%s"吗?',
|
||||
'Remove an automatic action' => '移除一个自动行为',
|
||||
'Close the task' => '关闭任务',
|
||||
'Assign the task to a specific user' => '将该任务指派给一个用户',
|
||||
'Assign the task to the person who does the action' => '将任务指派给产生该行为的用户',
|
||||
'Duplicate the task to another project' => '复制该任务到另一项目',
|
||||
'Move a task to another column' => '移动任务到另一栏目',
|
||||
'Move a task to another position in the same column' => '将任务移到该栏目另一位置',
|
||||
'Task modification' => '任务修改',
|
||||
'Task creation' => '任务创建',
|
||||
'Open a closed task' => '开启已关闭任务',
|
||||
'Closing a task' => '正在关闭任务',
|
||||
'Assign a color to a specific user' => '为特定用户指派颜色',
|
||||
'Column title' => '栏目名称',
|
||||
'Position' => '位置',
|
||||
'Move Up' => '往上移',
|
||||
'Move Down' => '往下移',
|
||||
'Duplicate to another project' => '复制到另一项目',
|
||||
'Duplicate' => '复制',
|
||||
'link' => '连接',
|
||||
'Update this comment' => '更新该评论',
|
||||
'Comment updated successfully.' => '评论成功更新。',
|
||||
'Unable to update your comment.' => '无法更新您的评论。',
|
||||
'Remove a comment' => '移除评论',
|
||||
'Comment removed successfully.' => '评论成功移除。',
|
||||
'Unable to remove this comment.' => '无法移除该评论。',
|
||||
'Do you really want to remove this comment?' => '确定要移除评论吗?',
|
||||
'Only administrators or the creator of the comment can access to this page.' => '只有管理员或评论创建者可以进入该页面。',
|
||||
'Details' => '细节',
|
||||
'Current password for the user "%s"' => '用户"%s"的当前密码',
|
||||
'The current password is required' => '需要输入当前密码',
|
||||
'Wrong password' => '密码错误',
|
||||
'Confirmation' => '再输一次新密码',
|
||||
'Reset all tokens' => '重置所有令牌',
|
||||
'All tokens have been regenerated.' => '所有令牌都重新生成了。',
|
||||
'Unknown' => '未知',
|
||||
'Last logins' => '上次登录',
|
||||
'Login date' => '登录日期',
|
||||
'Authentication method' => '认证方式',
|
||||
'IP address' => 'IP地址',
|
||||
'User agent' => '用户代理',
|
||||
'Persistent connections' => '持续连接',
|
||||
'No session' => '无会话',
|
||||
'Expiration date' => '过期',
|
||||
'Remember Me' => '记住我',
|
||||
'Creation date' => '创建日期',
|
||||
'Filter by user' => '按用户过滤',
|
||||
'Filter by due date' => '按到期时间过滤',
|
||||
'Everybody' => '所有人',
|
||||
'Open' => '打开',
|
||||
'Closed' => '关闭',
|
||||
'Search' => '查找',
|
||||
'Nothing found.' => '没找到。',
|
||||
'Search in the project "%s"' => '在项目"%s"中查找',
|
||||
'Due date' => '到期',
|
||||
'Others formats accepted: %s and %s' => '允许其他格式:%s和%s',
|
||||
'Description' => '描述',
|
||||
'%d comments' => '%d个评论',
|
||||
'%d comment' => '%d个评论',
|
||||
'Email address invalid' => 'Email地址无效',
|
||||
'Your Google Account is not linked anymore to your profile.' => '您的google帐号不再与您的账户配置关联。',
|
||||
'Unable to unlink your Google Account.' => '无法去除您google帐号的关联',
|
||||
'Google authentication failed' => 'google验证失败',
|
||||
'Unable to link your Google Account.' => '无法关联您的google帐号。',
|
||||
'Your Google Account is linked to your profile successfully.' => '您的google帐号已成功与账户配置关联。',
|
||||
'Email' => 'Email',
|
||||
'Link my Google Account' => '关联我的google帐号',
|
||||
'Unlink my Google Account' => '去除我的google帐号关联',
|
||||
'Login with my Google Account' => '用我的google帐号登录',
|
||||
'Project not found.' => '未发现项目',
|
||||
'Task #%d' => '任务 #%d',
|
||||
'Task removed successfully.' => '任务成功去除',
|
||||
'Unable to remove this task.' => '无法移除该任务。',
|
||||
'Remove a task' => '移除一个任务',
|
||||
'Do you really want to remove this task: "%s"?' => '确定要溢出该任务"%s"吗?',
|
||||
'Assign a color to a specific category' => '指派颜色给一个特定分类',
|
||||
'Task creation or modification' => '任务创建或修改',
|
||||
'Category' => '分类',
|
||||
'Category:' => '分类:',
|
||||
'Categories' => '分类',
|
||||
'Category not found.' => '未找到分类。',
|
||||
'Your category have been created successfully.' => '成功为您创建分类。',
|
||||
'Unable to create your category.' => '无法为您创建分类。',
|
||||
'Your category have been updated successfully.' => '成功为您更新分类。',
|
||||
'Unable to update your category.' => '无法为您更新分类。',
|
||||
'Remove a category' => '移除一个分类',
|
||||
'Category removed successfully.' => '分类成功移除。',
|
||||
'Unable to remove this category.' => '无法移除该分类。',
|
||||
'Category modification for the project "%s"' => '为项目"%s"修改分类',
|
||||
'Category Name' => '分类名称',
|
||||
'Categories for the project "%s"' => '项目"%s"的分类',
|
||||
'Add a new category' => '加入一个新分类',
|
||||
'Do you really want to remove this category: "%s"?' => '您确定要移除分类"%s"吗?',
|
||||
'Filter by category' => '按分类过滤',
|
||||
'All categories' => '所有分类',
|
||||
'No category' => '无分类',
|
||||
'The name is required' => '必须要有名字',
|
||||
'Remove a file' => '移除一个文件',
|
||||
'Unable to remove this file.' => '无法移除该文件。',
|
||||
'File removed successfully.' => '文件成功移除。',
|
||||
'Attach a document' => '附上一个文档',
|
||||
'Do you really want to remove this file: "%s"?' => '您确定要移除文件"%s"吗?',
|
||||
'open' => '打开',
|
||||
'Attachments' => '附件',
|
||||
'Edit the task' => '修改任务',
|
||||
'Edit the description' => '修改描述',
|
||||
'Add a comment' => '添加一个注释',
|
||||
'Edit a comment' => '修改一个注释',
|
||||
'Summary' => '概要',
|
||||
// 'Time tracking' => '',
|
||||
// 'Estimate:' => '',
|
||||
// 'Spent:' => '',
|
||||
// 'Do you really want to remove this sub-task?' => '',
|
||||
// 'Remaining:' => '',
|
||||
// 'hours' => '',
|
||||
// 'spent' => '',
|
||||
// 'estimated' => '',
|
||||
// 'Sub-Tasks' => '',
|
||||
// 'Add a sub-task' => '',
|
||||
// 'Original Estimate' => '',
|
||||
// 'Create another sub-task' => '',
|
||||
// 'Time Spent' => '',
|
||||
// 'Edit a sub-task' => '',
|
||||
// 'Remove a sub-task' => '',
|
||||
// 'The time must be a numeric value' => '',
|
||||
// 'Todo' => '',
|
||||
// 'In progress' => '',
|
||||
// 'Done' => '',
|
||||
// 'Sub-task removed successfully.' => '',
|
||||
// 'Unable to remove this sub-task.' => '',
|
||||
// 'Sub-task updated successfully.' => '',
|
||||
// 'Unable to update your sub-task.' => '',
|
||||
// 'Unable to create your sub-task.' => '',
|
||||
// 'Sub-task added successfully.' => '',
|
||||
// 'Maximum size: ' => '',
|
||||
// 'Unable to upload the file.' => '',
|
||||
// 'Display another project' => '',
|
||||
// 'Your GitHub account was successfully linked to your profile.' => '',
|
||||
// 'Unable to link your GitHub Account.' => '',
|
||||
// 'GitHub authentication failed' => '',
|
||||
// 'Your GitHub account is no longer linked to your profile.' => '',
|
||||
// 'Unable to unlink your GitHub Account.' => '',
|
||||
// 'Login with my GitHub Account' => '',
|
||||
// 'Link my GitHub Account' => '',
|
||||
// 'Unlink my GitHub Account' => '',
|
||||
// 'Created by %s' => 'Créé par %s',
|
||||
// 'Last modified on %B %e, %G at %k:%M %p' => '',
|
||||
);
|
176
sources/app/Model/Acl.php
Normal file
176
sources/app/Model/Acl.php
Normal file
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
/**
|
||||
* Acl model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Acl extends Base
|
||||
{
|
||||
/**
|
||||
* Controllers and actions allowed from outside
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $public_actions = array(
|
||||
'user' => array('login', 'check', 'google', 'github'),
|
||||
'task' => array('add'),
|
||||
'board' => array('readonly'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Controllers and actions allowed for regular users
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $user_actions = array(
|
||||
'app' => array('index'),
|
||||
'board' => array('index', 'show', 'assign', 'assigntask', 'save', 'check'),
|
||||
'project' => array('tasks', 'index', 'forbidden', 'search'),
|
||||
'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index', 'unlinkgoogle', 'unlinkgithub'),
|
||||
'config' => array('index', 'removeremembermetoken'),
|
||||
'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'),
|
||||
'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'),
|
||||
'subtask' => array('create', 'save', 'edit', 'update', 'confirm', 'remove'),
|
||||
'task' => array(
|
||||
'show',
|
||||
'create',
|
||||
'save',
|
||||
'edit',
|
||||
'update',
|
||||
'close',
|
||||
'confirmclose',
|
||||
'open',
|
||||
'confirmopen',
|
||||
'duplicate',
|
||||
'remove',
|
||||
'confirmremove',
|
||||
'editdescription',
|
||||
'savedescription',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Return true if the specified controller/action is allowed according to the given acl
|
||||
*
|
||||
* @access public
|
||||
* @param array $acl Acl list
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowedAction(array $acl, $controller, $action)
|
||||
{
|
||||
if (isset($acl[$controller])) {
|
||||
return in_array($action, $acl[$controller]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given action is public
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublicAction($controller, $action)
|
||||
{
|
||||
return $this->isAllowedAction($this->public_actions, $controller, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given action is allowed for a regular user
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @return bool
|
||||
*/
|
||||
public function isUserAction($controller, $action)
|
||||
{
|
||||
return $this->isAllowedAction($this->user_actions, $controller, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the logged user is admin
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isAdminUser()
|
||||
{
|
||||
return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the logged user is not admin
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isRegularUser()
|
||||
{
|
||||
return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connected user id
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
return isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is the user is connected
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isLogged()
|
||||
{
|
||||
return ! empty($_SESSION['user']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is the user was authenticated with the RememberMe or set the value
|
||||
*
|
||||
* @access public
|
||||
* @param bool $value Set true if the user use the RememberMe
|
||||
* @return bool
|
||||
*/
|
||||
public function isRememberMe($value = null)
|
||||
{
|
||||
if ($value !== null) {
|
||||
$_SESSION['is_remember_me'] = $value;
|
||||
}
|
||||
|
||||
return empty($_SESSION['is_remember_me']) ? false : $_SESSION['is_remember_me'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an action is allowed for the logged user
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @return bool
|
||||
*/
|
||||
public function isPageAccessAllowed($controller, $action)
|
||||
{
|
||||
return $this->isPublicAction($controller, $action) ||
|
||||
$this->isAdminUser() ||
|
||||
($this->isRegularUser() && $this->isUserAction($controller, $action));
|
||||
}
|
||||
}
|
273
sources/app/Model/Action.php
Normal file
273
sources/app/Model/Action.php
Normal file
|
@ -0,0 +1,273 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use LogicException;
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
|
||||
/**
|
||||
* Action model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Action extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name for actions
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'actions';
|
||||
|
||||
/**
|
||||
* SQL table name for action parameters
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_PARAMS = 'action_has_params';
|
||||
|
||||
/**
|
||||
* Return the name and description of available actions
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableActions()
|
||||
{
|
||||
return array(
|
||||
'TaskClose' => t('Close the task'),
|
||||
'TaskAssignSpecificUser' => t('Assign the task to a specific user'),
|
||||
'TaskAssignCurrentUser' => t('Assign the task to the person who does the action'),
|
||||
'TaskDuplicateAnotherProject' => t('Duplicate the task to another project'),
|
||||
'TaskAssignColorUser' => t('Assign a color to a specific user'),
|
||||
'TaskAssignColorCategory' => t('Assign automatically a color based on a category'),
|
||||
'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name and description of available actions
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableEvents()
|
||||
{
|
||||
return array(
|
||||
Task::EVENT_MOVE_COLUMN => t('Move a task to another column'),
|
||||
Task::EVENT_MOVE_POSITION => t('Move a task to another position in the same column'),
|
||||
Task::EVENT_UPDATE => t('Task modification'),
|
||||
Task::EVENT_CREATE => t('Task creation'),
|
||||
Task::EVENT_OPEN => t('Open a closed task'),
|
||||
Task::EVENT_CLOSE => t('Closing a task'),
|
||||
Task::EVENT_CREATE_UPDATE => t('Task creation or modification'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions and parameters for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param $project_id
|
||||
* @return array
|
||||
*/
|
||||
public function getAllByProject($project_id)
|
||||
{
|
||||
$actions = $this->db->table(self::TABLE)->eq('project_id', $project_id)->findAll();
|
||||
|
||||
foreach ($actions as &$action) {
|
||||
$action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action['id'])->findAll();
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all actions and parameters
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
$actions = $this->db->table(self::TABLE)->findAll();
|
||||
|
||||
foreach ($actions as &$action) {
|
||||
$action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action['id'])->findAll();
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all required action parameters for all registered actions
|
||||
*
|
||||
* @access public
|
||||
* @return array All required parameters for all actions
|
||||
*/
|
||||
public function getAllActionParameters()
|
||||
{
|
||||
$params = array();
|
||||
|
||||
foreach ($this->getAll() as $action) {
|
||||
|
||||
$action = $this->load($action['action_name'], $action['project_id']);
|
||||
$params += $action->getActionRequiredParameters();
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an action
|
||||
*
|
||||
* @access public
|
||||
* @param integer $action_id Action id
|
||||
* @return array Action data
|
||||
*/
|
||||
public function getById($action_id)
|
||||
{
|
||||
$action = $this->db->table(self::TABLE)->eq('id', $action_id)->findOne();
|
||||
$action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action_id)->findAll();
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an action
|
||||
*
|
||||
* @access public
|
||||
* @param integer $action_id Action id
|
||||
* @return bool Success or not
|
||||
*/
|
||||
public function remove($action_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $action_id)->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Required parameters to save an action
|
||||
* @return bool Success or not
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
$action = array(
|
||||
'project_id' => $values['project_id'],
|
||||
'event_name' => $values['event_name'],
|
||||
'action_name' => $values['action_name'],
|
||||
);
|
||||
|
||||
if (! $this->db->table(self::TABLE)->save($action)) {
|
||||
$this->db->cancelTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
$action_id = $this->db->getConnection()->getLastId();
|
||||
|
||||
foreach ($values['params'] as $param_name => $param_value) {
|
||||
|
||||
$action_param = array(
|
||||
'action_id' => $action_id,
|
||||
'name' => $param_name,
|
||||
'value' => $param_value,
|
||||
);
|
||||
|
||||
if (! $this->db->table(self::TABLE_PARAMS)->save($action_param)) {
|
||||
$this->db->cancelTransaction();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all actions and attach events
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function attachEvents()
|
||||
{
|
||||
foreach ($this->getAll() as $action) {
|
||||
|
||||
$listener = $this->load($action['action_name'], $action['project_id']);
|
||||
|
||||
foreach ($action['params'] as $param) {
|
||||
$listener->setParam($param['name'], $param['value']);
|
||||
}
|
||||
|
||||
$this->event->attach($action['event_name'], $listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an action
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Action class name
|
||||
* @param integer $project_id Project id
|
||||
* @throws \LogicException
|
||||
* @return \Core\Listener Action Instance
|
||||
* @throw LogicException
|
||||
*/
|
||||
public function load($name, $project_id)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'TaskClose':
|
||||
$className = '\Action\TaskClose';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
case 'TaskAssignCurrentUser':
|
||||
$className = '\Action\TaskAssignCurrentUser';
|
||||
return new $className($project_id, new Task($this->db, $this->event), new Acl($this->db, $this->event));
|
||||
case 'TaskAssignSpecificUser':
|
||||
$className = '\Action\TaskAssignSpecificUser';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
case 'TaskDuplicateAnotherProject':
|
||||
$className = '\Action\TaskDuplicateAnotherProject';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
case 'TaskAssignColorUser':
|
||||
$className = '\Action\TaskAssignColorUser';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
case 'TaskAssignColorCategory':
|
||||
$className = '\Action\TaskAssignColorCategory';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
case 'TaskAssignCategoryColor':
|
||||
$className = '\Action\TaskAssignCategoryColor';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
default:
|
||||
throw new LogicException('Action not found: '.$name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate action creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Required parameters to save an action
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('project_id', t('The project id is required')),
|
||||
new Validators\Integer('project_id', t('This value must be an integer')),
|
||||
new Validators\Required('event_name', t('This value is required')),
|
||||
new Validators\Required('action_name', t('This value is required')),
|
||||
new Validators\Required('params', t('This value is required')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
}
|
58
sources/app/Model/Base.php
Normal file
58
sources/app/Model/Base.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validator.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Base.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Required.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Unique.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/MaxLength.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/MinLength.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Integer.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Equals.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/AlphaNumeric.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/GreaterThan.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Date.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Email.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Numeric.php';
|
||||
|
||||
use Core\Event;
|
||||
use PicoDb\Database;
|
||||
|
||||
/**
|
||||
* Base model class
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class Base
|
||||
{
|
||||
/**
|
||||
* Database instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \PicoDb\Database
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Event dispatcher instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \Core\Event
|
||||
*/
|
||||
protected $event;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \PicoDb\Database $db Database instance
|
||||
* @param \Core\Event $event Event dispatcher instance
|
||||
*/
|
||||
public function __construct(Database $db, Event $event)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->event = $event;
|
||||
}
|
||||
}
|
359
sources/app/Model/Board.php
Normal file
359
sources/app/Model/Board.php
Normal file
|
@ -0,0 +1,359 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
|
||||
/**
|
||||
* Board model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Board extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'columns';
|
||||
|
||||
/**
|
||||
* Save task positions for each column
|
||||
*
|
||||
* @access public
|
||||
* @param array $values [['task_id' => X, 'column_id' => X, 'position' => X], ...]
|
||||
* @return boolean
|
||||
*/
|
||||
public function saveTasksPosition(array $values)
|
||||
{
|
||||
$taskModel = new Task($this->db, $this->event);
|
||||
|
||||
$this->db->startTransaction();
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (! $taskModel->move($value['task_id'], $value['column_id'], $value['position'])) {
|
||||
$this->db->cancelTransaction();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a board with default columns, must be executed inside a transaction
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param array $columns List of columns title ['column1', 'column2', ...]
|
||||
* @return boolean
|
||||
*/
|
||||
public function create($project_id, array $columns)
|
||||
{
|
||||
$position = 0;
|
||||
|
||||
foreach ($columns as $title) {
|
||||
|
||||
$values = array(
|
||||
'title' => $title,
|
||||
'position' => ++$position,
|
||||
'project_id' => $project_id,
|
||||
);
|
||||
|
||||
if (! $this->db->table(self::TABLE)->save($values)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new column to the board
|
||||
*
|
||||
* @access public
|
||||
* @param array $values ['title' => X, 'project_id' => X]
|
||||
* @return boolean
|
||||
*/
|
||||
public function add(array $values)
|
||||
{
|
||||
$values['position'] = $this->getLastColumnPosition($values['project_id']) + 1;
|
||||
return $this->db->table(self::TABLE)->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update columns
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return boolean
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
foreach (array('title', 'task_limit') as $field) {
|
||||
foreach ($values[$field] as $column_id => $field_value) {
|
||||
|
||||
if ($field === 'task_limit' && empty($field_value)) {
|
||||
$field_value = 0;
|
||||
}
|
||||
|
||||
$this->updateColumn($column_id, array($field => $field_value));
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a column
|
||||
*
|
||||
* @access public
|
||||
* @param integer $column_id Column id
|
||||
* @param array $values Form values
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateColumn($column_id, array $values)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $column_id)->update($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a column down, increment the column position value
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $column_id Column id
|
||||
* @return boolean
|
||||
*/
|
||||
public function moveDown($project_id, $column_id)
|
||||
{
|
||||
$columns = $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'position');
|
||||
$positions = array_flip($columns);
|
||||
|
||||
if (isset($columns[$column_id]) && $columns[$column_id] < count($columns)) {
|
||||
|
||||
$position = ++$columns[$column_id];
|
||||
$columns[$positions[$position]]--;
|
||||
|
||||
$this->db->startTransaction();
|
||||
$this->db->table(self::TABLE)->eq('id', $column_id)->update(array('position' => $position));
|
||||
$this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $columns[$positions[$position]]));
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a column up, decrement the column position value
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $column_id Column id
|
||||
* @return boolean
|
||||
*/
|
||||
public function moveUp($project_id, $column_id)
|
||||
{
|
||||
$columns = $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'position');
|
||||
$positions = array_flip($columns);
|
||||
|
||||
if (isset($columns[$column_id]) && $columns[$column_id] > 1) {
|
||||
|
||||
$position = --$columns[$column_id];
|
||||
$columns[$positions[$position]]++;
|
||||
|
||||
$this->db->startTransaction();
|
||||
$this->db->table(self::TABLE)->eq('id', $column_id)->update(array('position' => $position));
|
||||
$this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $columns[$positions[$position]]));
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all columns and tasks for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param array $filters
|
||||
* @return array
|
||||
*/
|
||||
public function get($project_id, array $filters = array())
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
$columns = $this->getColumns($project_id);
|
||||
|
||||
$filters[] = array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id);
|
||||
$filters[] = array('column' => 'is_active', 'operator' => 'eq', 'value' => Task::STATUS_OPEN);
|
||||
|
||||
$taskModel = new Task($this->db, $this->event);
|
||||
$tasks = $taskModel->find($filters);
|
||||
|
||||
foreach ($columns as &$column) {
|
||||
|
||||
$column['tasks'] = array();
|
||||
|
||||
foreach ($tasks as &$task) {
|
||||
if ($task['column_id'] == $column['id']) {
|
||||
$column['tasks'][] = $task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first column id for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return integer
|
||||
*/
|
||||
public function getFirstColumn($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findOneColumn('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of columns sorted by position [ column_id => title ]
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function getColumnsList($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'title');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all columns sorted by position for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of columns for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return integer
|
||||
*/
|
||||
public function countColumns($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a column by the id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $column_id Column id
|
||||
* @return array
|
||||
*/
|
||||
public function getColumn($column_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $column_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of the last column for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return integer
|
||||
*/
|
||||
public function getLastColumnPosition($project_id)
|
||||
{
|
||||
return (int) $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('project_id', $project_id)
|
||||
->desc('position')
|
||||
->findOneColumn('position');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a column and all tasks associated to this column
|
||||
*
|
||||
* @access public
|
||||
* @param integer $column_id Column id
|
||||
* @return boolean
|
||||
*/
|
||||
public function removeColumn($column_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $column_id)->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate column modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $columns Original columns List
|
||||
* @param array $values Required parameters to update a column
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateModification(array $columns, array $values)
|
||||
{
|
||||
$rules = array();
|
||||
|
||||
foreach ($columns as $column_id => $column_title) {
|
||||
$rules[] = new Validators\Integer('task_limit['.$column_id.']', t('This value must be an integer'));
|
||||
$rules[] = new Validators\GreaterThan('task_limit['.$column_id.']', t('This value must be greater than %d', 0), 0);
|
||||
$rules[] = new Validators\Required('title['.$column_id.']', t('The title is required'));
|
||||
$rules[] = new Validators\MaxLength('title['.$column_id.']', t('The maximum length is %d characters', 50), 50);
|
||||
}
|
||||
|
||||
$v = new Validator($values, $rules);
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate column creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Required parameters to save an action
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('project_id', t('The project id is required')),
|
||||
new Validators\Integer('project_id', t('This value must be an integer')),
|
||||
new Validators\Required('title', t('The title is required')),
|
||||
new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
}
|
165
sources/app/Model/Category.php
Normal file
165
sources/app/Model/Category.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
|
||||
/**
|
||||
* Category model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Category extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'project_has_categories';
|
||||
|
||||
/**
|
||||
* Get a category by the id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $category_id Category id
|
||||
* @return array
|
||||
*/
|
||||
public function getById($category_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $category_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of all categories
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param bool $prepend_none If true, prepend to the list the value 'None'
|
||||
* @param bool $prepend_all If true, prepend to the list the value 'All'
|
||||
* @return array
|
||||
*/
|
||||
public function getList($project_id, $prepend_none = true, $prepend_all = false)
|
||||
{
|
||||
$listing = $this->db->table(self::TABLE)
|
||||
->eq('project_id', $project_id)
|
||||
->asc('name')
|
||||
->listing('id', 'name');
|
||||
|
||||
$prepend = array();
|
||||
|
||||
if ($prepend_all) {
|
||||
$prepend[-1] = t('All categories');
|
||||
}
|
||||
|
||||
if ($prepend_none) {
|
||||
$prepend[0] = t('No category');
|
||||
}
|
||||
|
||||
return $prepend + $listing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all categories for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)
|
||||
->eq('project_id', $project_id)
|
||||
->asc('name')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a category
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a category
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a category
|
||||
*
|
||||
* @access public
|
||||
* @param integer $category_id Category id
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($category_id)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
$r1 = $this->db->table(Task::TABLE)->eq('category_id', $category_id)->update(array('category_id' => 0));
|
||||
$r2 = $this->db->table(self::TABLE)->eq('id', $category_id)->remove();
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return $r1 && $r2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate category creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('project_id', t('The project id is required')),
|
||||
new Validators\Integer('project_id', t('The project id must be an integer')),
|
||||
new Validators\Required('name', t('The name is required')),
|
||||
new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50)
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate category modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateModification(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('The id is required')),
|
||||
new Validators\Integer('id', t('The id must be an integer')),
|
||||
new Validators\Required('project_id', t('The project id is required')),
|
||||
new Validators\Integer('project_id', t('The project id must be an integer')),
|
||||
new Validators\Required('name', t('The name is required')),
|
||||
new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50)
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
}
|
171
sources/app/Model/Comment.php
Normal file
171
sources/app/Model/Comment.php
Normal file
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
|
||||
/**
|
||||
* Comment model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Comment extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'comments';
|
||||
|
||||
/**
|
||||
* Get all comments for a given task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($task_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->columns(
|
||||
self::TABLE.'.id',
|
||||
self::TABLE.'.date',
|
||||
self::TABLE.'.task_id',
|
||||
self::TABLE.'.user_id',
|
||||
self::TABLE.'.comment',
|
||||
User::TABLE.'.username'
|
||||
)
|
||||
->join(User::TABLE, 'id', 'user_id')
|
||||
->orderBy(self::TABLE.'.date', 'ASC')
|
||||
->eq(self::TABLE.'.task_id', $task_id)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a comment
|
||||
*
|
||||
* @access public
|
||||
* @param integer $comment_id Comment id
|
||||
* @return array
|
||||
*/
|
||||
public function getById($comment_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->columns(
|
||||
self::TABLE.'.id',
|
||||
self::TABLE.'.task_id',
|
||||
self::TABLE.'.user_id',
|
||||
self::TABLE.'.date',
|
||||
self::TABLE.'.comment',
|
||||
User::TABLE.'.username'
|
||||
)
|
||||
->join(User::TABLE, 'id', 'user_id')
|
||||
->eq(self::TABLE.'.id', $comment_id)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of comments for a given task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return integer
|
||||
*/
|
||||
public function count($task_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq(self::TABLE.'.task_id', $task_id)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a comment in the database
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return boolean
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
$values['date'] = time();
|
||||
|
||||
return $this->db->table(self::TABLE)->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a comment in the database
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return boolean
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $values['id'])
|
||||
->update(array('comment' => $values['comment']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a comment
|
||||
*
|
||||
* @access public
|
||||
* @param integer $comment_id Comment id
|
||||
* @return boolean
|
||||
*/
|
||||
public function remove($comment_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $comment_id)->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate comment creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Required parameters to save an action
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('task_id', t('This value is required')),
|
||||
new Validators\Integer('task_id', t('This value must be an integer')),
|
||||
new Validators\Required('user_id', t('This value is required')),
|
||||
new Validators\Integer('user_id', t('This value must be an integer')),
|
||||
new Validators\Required('comment', t('Comment is required'))
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate comment modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Required parameters to save an action
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateModification(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('This value is required')),
|
||||
new Validators\Integer('id', t('This value must be an integer')),
|
||||
new Validators\Required('comment', t('Comment is required'))
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
}
|
193
sources/app/Model/File.php
Normal file
193
sources/app/Model/File.php
Normal file
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
/**
|
||||
* File model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class File extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'task_has_files';
|
||||
|
||||
/**
|
||||
* Directory where are stored files
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BASE_PATH = 'data/files/';
|
||||
|
||||
/**
|
||||
* Get a file by the id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $file_id File id
|
||||
* @return array
|
||||
*/
|
||||
public function getById($file_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $file_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a file
|
||||
*
|
||||
* @access public
|
||||
* @param integer $file_id File id
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($file_id)
|
||||
{
|
||||
$file = $this->getbyId($file_id);
|
||||
|
||||
if (! empty($file) && @unlink(self::BASE_PATH.$file['path'])) {
|
||||
return $this->db->table(self::TABLE)->eq('id', $file_id)->remove();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all files for a given task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return bool
|
||||
*/
|
||||
public function removeAll($task_id)
|
||||
{
|
||||
$files = $this->getAll($task_id);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->remove($file['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file entry in the database
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @param string $name Filename
|
||||
* @param string $path Path on the disk
|
||||
* @param bool $is_image Image or not
|
||||
* @return bool
|
||||
*/
|
||||
public function create($task_id, $name, $path, $is_image)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->save(array(
|
||||
'task_id' => $task_id,
|
||||
'name' => $name,
|
||||
'path' => $path,
|
||||
'is_image' => $is_image ? '1' : '0',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all files for a given task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($task_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)
|
||||
->eq('task_id', $task_id)
|
||||
->asc('name')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a filename is an image
|
||||
*
|
||||
* @access public
|
||||
* @param string $filename Filename
|
||||
* @return bool
|
||||
*/
|
||||
public function isImage($filename)
|
||||
{
|
||||
return getimagesize($filename) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the path for a new filename
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $task_id Task id
|
||||
* @param string $filename Filename
|
||||
* @return string
|
||||
*/
|
||||
public function generatePath($project_id, $task_id, $filename)
|
||||
{
|
||||
return $project_id.DIRECTORY_SEPARATOR.$task_id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the base directory is created correctly
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
if (! is_dir(self::BASE_PATH)) {
|
||||
if (! mkdir(self::BASE_PATH, 0755, true)) {
|
||||
die('Unable to create the upload directory: "'.self::BASE_PATH.'"');
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_writable(self::BASE_PATH)) {
|
||||
die('The directory "'.self::BASE_PATH.'" must be writeable by your webserver user');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle file upload
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $task_id Task id
|
||||
* @param string $form_name File form name
|
||||
* @return bool
|
||||
*/
|
||||
public function upload($project_id, $task_id, $form_name)
|
||||
{
|
||||
$this->setup();
|
||||
$result = array();
|
||||
|
||||
if (! empty($_FILES[$form_name])) {
|
||||
|
||||
foreach ($_FILES[$form_name]['error'] as $key => $error) {
|
||||
|
||||
if ($error == UPLOAD_ERR_OK && $_FILES[$form_name]['size'][$key] > 0) {
|
||||
|
||||
$original_filename = basename($_FILES[$form_name]['name'][$key]);
|
||||
$uploaded_filename = $_FILES[$form_name]['tmp_name'][$key];
|
||||
$destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
|
||||
|
||||
@mkdir(self::BASE_PATH.dirname($destination_filename), 0755, true);
|
||||
|
||||
if (@move_uploaded_file($uploaded_filename, self::BASE_PATH.$destination_filename)) {
|
||||
|
||||
$result[] = $this->create(
|
||||
$task_id,
|
||||
$original_filename,
|
||||
$destination_filename,
|
||||
$this->isImage(self::BASE_PATH.$destination_filename)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count(array_unique($result)) === 1;
|
||||
}
|
||||
}
|
178
sources/app/Model/GitHub.php
Normal file
178
sources/app/Model/GitHub.php
Normal file
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
require __DIR__.'/../../vendor/OAuth/bootstrap.php';
|
||||
|
||||
use OAuth\Common\Storage\Session;
|
||||
use OAuth\Common\Consumer\Credentials;
|
||||
use OAuth\Common\Http\Uri\UriFactory;
|
||||
use OAuth\ServiceFactory;
|
||||
use OAuth\Common\Http\Exception\TokenResponseException;
|
||||
|
||||
/**
|
||||
* GitHub model
|
||||
*
|
||||
* @package model
|
||||
*/
|
||||
class GitHub extends Base
|
||||
{
|
||||
/**
|
||||
* Authenticate a GitHub user
|
||||
*
|
||||
* @access public
|
||||
* @param string $github_id GitHub user id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($github_id)
|
||||
{
|
||||
$userModel = new User($this->db, $this->event);
|
||||
|
||||
$user = $userModel->getByGitHubId($github_id);
|
||||
|
||||
if ($user) {
|
||||
|
||||
// Create the user session
|
||||
$userModel->updateSession($user);
|
||||
|
||||
// Update login history
|
||||
$lastLogin = new LastLogin($this->db, $this->event);
|
||||
$lastLogin->create(
|
||||
LastLogin::AUTH_GITHUB,
|
||||
$user['id'],
|
||||
$userModel->getIpAddress(),
|
||||
$userModel->getUserAgent()
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a GitHub account for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unlink($user_id)
|
||||
{
|
||||
$userModel = new User($this->db, $this->event);
|
||||
|
||||
return $userModel->update(array(
|
||||
'id' => $user_id,
|
||||
'github_id' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user table based on the GitHub profile information
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param array $profile GitHub profile
|
||||
* @return boolean
|
||||
* @todo Don't overwrite existing email/name with empty GitHub data
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$userModel = new User($this->db, $this->event);
|
||||
|
||||
return $userModel->update(array(
|
||||
'id' => $user_id,
|
||||
'github_id' => $profile['id'],
|
||||
'email' => $profile['email'],
|
||||
'name' => $profile['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GitHub service instance
|
||||
*
|
||||
* @access public
|
||||
* @return \OAuth\OAuth2\Service\GitHub
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
$uriFactory = new UriFactory();
|
||||
$currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
|
||||
$currentUri->setQuery('controller=user&action=gitHub');
|
||||
|
||||
$storage = new Session(false);
|
||||
|
||||
$credentials = new Credentials(
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
$currentUri->getAbsoluteUri()
|
||||
);
|
||||
|
||||
$serviceFactory = new ServiceFactory();
|
||||
|
||||
return $serviceFactory->createService(
|
||||
'gitHub',
|
||||
$credentials,
|
||||
$storage,
|
||||
array('')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorization URL
|
||||
*
|
||||
* @access public
|
||||
* @return \OAuth\Common\Http\Uri\Uri
|
||||
*/
|
||||
public function getAuthorizationUrl()
|
||||
{
|
||||
return $this->getService()->getAuthorizationUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GitHub profile information from the API
|
||||
*
|
||||
* @access public
|
||||
* @param string $code GitHub authorization code
|
||||
* @return bool|array
|
||||
*/
|
||||
public function getGitHubProfile($code)
|
||||
{
|
||||
try {
|
||||
$gitHubService = $this->getService();
|
||||
$gitHubService->requestAccessToken($code);
|
||||
|
||||
return json_decode($gitHubService->request('user'), true);
|
||||
}
|
||||
catch (TokenResponseException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes this user's GitHub tokens for Kanboard
|
||||
*
|
||||
* @access public
|
||||
* @return bool|array
|
||||
* @todo Currently this simply removes all our tokens for this user, ideally it should
|
||||
* restrict itself to the one in question
|
||||
*/
|
||||
public function revokeGitHubAccess()
|
||||
{
|
||||
try {
|
||||
$gitHubService = $this->getService();
|
||||
|
||||
$basicAuthHeader = array('Authorization' => 'Basic ' .
|
||||
base64_encode(GITHUB_CLIENT_ID.':'.GITHUB_CLIENT_SECRET));
|
||||
|
||||
return json_decode($gitHubService->request('/applications/'.GITHUB_CLIENT_ID.'/tokens', 'DELETE', null, $basicAuthHeader), true);
|
||||
}
|
||||
catch (TokenResponseException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
152
sources/app/Model/Google.php
Normal file
152
sources/app/Model/Google.php
Normal file
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
require __DIR__.'/../../vendor/OAuth/bootstrap.php';
|
||||
|
||||
use OAuth\Common\Storage\Session;
|
||||
use OAuth\Common\Consumer\Credentials;
|
||||
use OAuth\Common\Http\Uri\UriFactory;
|
||||
use OAuth\ServiceFactory;
|
||||
use OAuth\Common\Http\Exception\TokenResponseException;
|
||||
|
||||
/**
|
||||
* Google model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Google extends Base
|
||||
{
|
||||
/**
|
||||
* Authenticate a Google user
|
||||
*
|
||||
* @access public
|
||||
* @param string $google_id Google unique id
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($google_id)
|
||||
{
|
||||
$userModel = new User($this->db, $this->event);
|
||||
$user = $userModel->getByGoogleId($google_id);
|
||||
|
||||
if ($user) {
|
||||
|
||||
// Create the user session
|
||||
$userModel->updateSession($user);
|
||||
|
||||
// Update login history
|
||||
$lastLogin = new LastLogin($this->db, $this->event);
|
||||
$lastLogin->create(
|
||||
LastLogin::AUTH_GOOGLE,
|
||||
$user['id'],
|
||||
$userModel->getIpAddress(),
|
||||
$userModel->getUserAgent()
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a Google account for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unlink($user_id)
|
||||
{
|
||||
$userModel = new User($this->db, $this->event);
|
||||
|
||||
return $userModel->update(array(
|
||||
'id' => $user_id,
|
||||
'google_id' => '',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user table based on the Google profile information
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param array $profile Google profile
|
||||
* @return boolean
|
||||
*/
|
||||
public function updateUser($user_id, array $profile)
|
||||
{
|
||||
$userModel = new User($this->db, $this->event);
|
||||
|
||||
return $userModel->update(array(
|
||||
'id' => $user_id,
|
||||
'google_id' => $profile['id'],
|
||||
'email' => $profile['email'],
|
||||
'name' => $profile['name'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Google service instance
|
||||
*
|
||||
* @access public
|
||||
* @return \OAuth\OAuth2\Service\Google
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
$uriFactory = new UriFactory();
|
||||
$currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
|
||||
$currentUri->setQuery('controller=user&action=google');
|
||||
|
||||
$storage = new Session(false);
|
||||
|
||||
$credentials = new Credentials(
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
$currentUri->getAbsoluteUri()
|
||||
);
|
||||
|
||||
$serviceFactory = new ServiceFactory();
|
||||
|
||||
return $serviceFactory->createService(
|
||||
'google',
|
||||
$credentials,
|
||||
$storage,
|
||||
array('userinfo_email', 'userinfo_profile')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorization URL
|
||||
*
|
||||
* @access public
|
||||
* @return \OAuth\Common\Http\Uri\Uri
|
||||
*/
|
||||
public function getAuthorizationUrl()
|
||||
{
|
||||
return $this->getService()->getAuthorizationUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Google profile information from the API
|
||||
*
|
||||
* @access public
|
||||
* @param string $code Google authorization code
|
||||
* @return bool|array
|
||||
*/
|
||||
public function getGoogleProfile($code)
|
||||
{
|
||||
try {
|
||||
|
||||
$googleService = $this->getService();
|
||||
$googleService->requestAccessToken($code);
|
||||
return json_decode($googleService->request('https://www.googleapis.com/oauth2/v1/userinfo'), true);
|
||||
}
|
||||
catch (TokenResponseException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
92
sources/app/Model/LastLogin.php
Normal file
92
sources/app/Model/LastLogin.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
/**
|
||||
* LastLogin model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class LastLogin extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'last_logins';
|
||||
|
||||
/**
|
||||
* Number of connections to keep for history
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const NB_LOGINS = 10;
|
||||
|
||||
/**
|
||||
* Authentication methods
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const AUTH_DATABASE = 'database';
|
||||
const AUTH_REMEMBER_ME = 'remember_me';
|
||||
const AUTH_LDAP = 'ldap';
|
||||
const AUTH_GOOGLE = 'google';
|
||||
const AUTH_GITHUB = 'github';
|
||||
|
||||
/**
|
||||
* Create a new record
|
||||
*
|
||||
* @access public
|
||||
* @param string $auth_type Authentication method
|
||||
* @param integer $user_id User id
|
||||
* @param string $ip IP Address
|
||||
* @param string $user_agent User Agent
|
||||
* @return array
|
||||
*/
|
||||
public function create($auth_type, $user_id, $ip, $user_agent)
|
||||
{
|
||||
// Cleanup old sessions if necessary
|
||||
$connections = $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->desc('date_creation')
|
||||
->findAllByColumn('id');
|
||||
|
||||
if (count($connections) >= self::NB_LOGINS) {
|
||||
|
||||
$this->db->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->notin('id', array_slice($connections, 0, self::NB_LOGINS - 1))
|
||||
->remove();
|
||||
}
|
||||
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->insert(array(
|
||||
'auth_type' => $auth_type,
|
||||
'user_id' => $user_id,
|
||||
'ip' => $ip,
|
||||
'user_agent' => $user_agent,
|
||||
'date_creation' => time(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last connections for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->desc('date_creation')
|
||||
->columns('id', 'auth_type', 'ip', 'user_agent', 'date_creation')
|
||||
->findAll();
|
||||
}
|
||||
}
|
105
sources/app/Model/Ldap.php
Normal file
105
sources/app/Model/Ldap.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
/**
|
||||
* LDAP model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Ldap extends Base
|
||||
{
|
||||
/**
|
||||
* Authenticate a user
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return null|boolean
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
if (! function_exists('ldap_connect')) {
|
||||
die('The PHP LDAP extension is required');
|
||||
}
|
||||
|
||||
// Skip SSL certificate verification
|
||||
if (! LDAP_SSL_VERIFY) {
|
||||
putenv('LDAPTLS_REQCERT=never');
|
||||
}
|
||||
|
||||
$ldap = ldap_connect(LDAP_SERVER, LDAP_PORT);
|
||||
|
||||
if (! is_resource($ldap)) {
|
||||
die('Unable to connect to the LDAP server: "'.LDAP_SERVER.'"');
|
||||
}
|
||||
|
||||
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
|
||||
|
||||
if (! @ldap_bind($ldap, LDAP_USERNAME, LDAP_PASSWORD)) {
|
||||
die('Unable to bind to the LDAP server: "'.LDAP_SERVER.'"');
|
||||
}
|
||||
|
||||
$sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, sprintf(LDAP_USER_PATTERN, $username), array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL));
|
||||
|
||||
if ($sr === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info = ldap_get_entries($ldap, $sr);
|
||||
|
||||
// User not found
|
||||
if (count($info) == 0 || $info['count'] == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (@ldap_bind($ldap, $info[0]['dn'], $password)) {
|
||||
return $this->create($username, $info[0][LDAP_ACCOUNT_FULLNAME][0], $info[0][LDAP_ACCOUNT_EMAIL][0]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create automatically a new local user after the LDAP authentication
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $name Name of the user
|
||||
* @param string $email Email address
|
||||
* @return bool
|
||||
*/
|
||||
public function create($username, $name, $email)
|
||||
{
|
||||
$userModel = new User($this->db, $this->event);
|
||||
$user = $userModel->getByUsername($username);
|
||||
|
||||
// There is an existing user account
|
||||
if ($user) {
|
||||
|
||||
if ($user['is_ldap_user'] == 1) {
|
||||
|
||||
// LDAP user already created
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
||||
// There is already a local user with that username
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a LDAP user
|
||||
$values = array(
|
||||
'username' => $username,
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'is_admin' => 0,
|
||||
'is_ldap_user' => 1,
|
||||
);
|
||||
|
||||
return $userModel->create($values);
|
||||
}
|
||||
}
|
584
sources/app/Model/Project.php
Normal file
584
sources/app/Model/Project.php
Normal file
|
@ -0,0 +1,584 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
use Event\TaskModification;
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* Project model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Project extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name for projects
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'projects';
|
||||
|
||||
/**
|
||||
* SQL table name for users
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_USERS = 'project_has_users';
|
||||
|
||||
/**
|
||||
* Value for active project
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ACTIVE = 1;
|
||||
|
||||
/**
|
||||
* Value for inactive project
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const INACTIVE = 0;
|
||||
|
||||
/**
|
||||
* Get a list of people that can be assigned for tasks
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param bool $prepend_unassigned Prepend the 'Unassigned' value
|
||||
* @param bool $prepend_everybody Prepend the 'Everbody' value
|
||||
* @return array
|
||||
*/
|
||||
public function getUsersList($project_id, $prepend_unassigned = true, $prepend_everybody = false)
|
||||
{
|
||||
$allowed_users = $this->getAllowedUsers($project_id);
|
||||
$userModel = new User($this->db, $this->event);
|
||||
|
||||
if (empty($allowed_users)) {
|
||||
$allowed_users = $userModel->getList();
|
||||
}
|
||||
|
||||
if ($prepend_unassigned) {
|
||||
$allowed_users = array(t('Unassigned')) + $allowed_users;
|
||||
}
|
||||
|
||||
if ($prepend_everybody) {
|
||||
$allowed_users = array(User::EVERYBODY_ID => t('Everybody')) + $allowed_users;
|
||||
}
|
||||
|
||||
return $allowed_users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of allowed people for a project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedUsers($project_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE_USERS)
|
||||
->join(User::TABLE, 'id', 'user_id')
|
||||
->eq('project_id', $project_id)
|
||||
->asc('username')
|
||||
->listing('user_id', 'username');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed and not allowed users for a project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function getAllUsers($project_id)
|
||||
{
|
||||
$users = array(
|
||||
'allowed' => array(),
|
||||
'not_allowed' => array(),
|
||||
);
|
||||
|
||||
$userModel = new User($this->db, $this->event);
|
||||
$all_users = $userModel->getList();
|
||||
|
||||
$users['allowed'] = $this->getAllowedUsers($project_id);
|
||||
|
||||
foreach ($all_users as $user_id => $username) {
|
||||
|
||||
if (! isset($users['allowed'][$user_id])) {
|
||||
$users['not_allowed'][$user_id] = $username;
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow a specific user for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $user_id User id
|
||||
* @return bool
|
||||
*/
|
||||
public function allowUser($project_id, $user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE_USERS)
|
||||
->save(array('project_id' => $project_id, 'user_id' => $user_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a specific user for a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $user_id User id
|
||||
* @return bool
|
||||
*/
|
||||
public function revokeUser($project_id, $user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE_USERS)
|
||||
->eq('project_id', $project_id)
|
||||
->eq('user_id', $user_id)
|
||||
->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific user is allowed to access to a given project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $user_id User id
|
||||
* @return bool
|
||||
*/
|
||||
public function isUserAllowed($project_id, $user_id)
|
||||
{
|
||||
// If there is nobody specified, everybody have access to the project
|
||||
$nb_users = $this->db
|
||||
->table(self::TABLE_USERS)
|
||||
->eq('project_id', $project_id)
|
||||
->count();
|
||||
|
||||
if ($nb_users < 1) return true;
|
||||
|
||||
// Check if user has admin rights
|
||||
$nb_users = $this->db
|
||||
->table(User::TABLE)
|
||||
->eq('id', $user_id)
|
||||
->eq('is_admin', 1)
|
||||
->count();
|
||||
|
||||
if ($nb_users > 0) return true;
|
||||
|
||||
// Otherwise, allow only specific users
|
||||
return (bool) $this->db
|
||||
->table(self::TABLE_USERS)
|
||||
->eq('project_id', $project_id)
|
||||
->eq('user_id', $user_id)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a project by the id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function getById($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $project_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a project by the name
|
||||
*
|
||||
* @access public
|
||||
* @param string $project_name Project name
|
||||
* @return array
|
||||
*/
|
||||
public function getByName($project_name)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('name', $project_name)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch project data by using the token
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Token
|
||||
* @return array
|
||||
*/
|
||||
public function getByToken($token)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('token', $token)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first project from the database (no sorting)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getFirst()
|
||||
{
|
||||
return $this->db->table(self::TABLE)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all projects, optionaly fetch stats for each project and can check users permissions
|
||||
*
|
||||
* @access public
|
||||
* @param bool $fetch_stats If true, return metrics about each projects
|
||||
* @param bool $check_permissions If true, remove projects not allowed for the current user
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($fetch_stats = false, $check_permissions = false)
|
||||
{
|
||||
if (! $fetch_stats) {
|
||||
return $this->db->table(self::TABLE)->asc('name')->findAll();
|
||||
}
|
||||
|
||||
$this->db->startTransaction();
|
||||
|
||||
$projects = $this->db
|
||||
->table(self::TABLE)
|
||||
->asc('name')
|
||||
->findAll();
|
||||
|
||||
$boardModel = new Board($this->db, $this->event);
|
||||
$taskModel = new Task($this->db, $this->event);
|
||||
$aclModel = new Acl($this->db, $this->event);
|
||||
|
||||
foreach ($projects as $pkey => &$project) {
|
||||
|
||||
if ($check_permissions && ! $this->isUserAllowed($project['id'], $aclModel->getUserId())) {
|
||||
unset($projects[$pkey]);
|
||||
}
|
||||
else {
|
||||
|
||||
$columns = $boardModel->getcolumns($project['id']);
|
||||
$project['nb_active_tasks'] = 0;
|
||||
|
||||
foreach ($columns as &$column) {
|
||||
$column['nb_active_tasks'] = $taskModel->countByColumnId($project['id'], $column['id']);
|
||||
$project['nb_active_tasks'] += $column['nb_active_tasks'];
|
||||
}
|
||||
|
||||
$project['columns'] = $columns;
|
||||
$project['nb_tasks'] = $taskModel->countByProjectId($project['id']);
|
||||
$project['nb_inactive_tasks'] = $project['nb_tasks'] - $project['nb_active_tasks'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of all projects
|
||||
*
|
||||
* @access public
|
||||
* @param bool $prepend If true, prepend to the list the value 'None'
|
||||
* @return array
|
||||
*/
|
||||
public function getList($prepend = true)
|
||||
{
|
||||
if ($prepend) {
|
||||
return array(t('None')) + $this->db->table(self::TABLE)->asc('name')->listing('id', 'name');
|
||||
}
|
||||
|
||||
return $this->db->table(self::TABLE)->asc('name')->listing('id', 'name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all projects with all its data for a given status
|
||||
*
|
||||
* @access public
|
||||
* @param integer $status Proejct status: self::ACTIVE or self:INACTIVE
|
||||
* @return array
|
||||
*/
|
||||
public function getAllByStatus($status)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->asc('name')
|
||||
->eq('is_active', $status)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of project by status
|
||||
*
|
||||
* @access public
|
||||
* @param integer $status Project status: self::ACTIVE or self:INACTIVE
|
||||
* @return array
|
||||
*/
|
||||
public function getListByStatus($status)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->asc('name')
|
||||
->eq('is_active', $status)
|
||||
->listing('id', 'name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of projects by status
|
||||
*
|
||||
* @access public
|
||||
* @param integer $status Status: self::ACTIVE or self:INACTIVE
|
||||
* @return integer
|
||||
*/
|
||||
public function countByStatus($status)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('is_active', $status)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of projects for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param array $projects Project list: ['project_id' => 'project_name']
|
||||
* @param integer $user_id User id
|
||||
* @return array
|
||||
*/
|
||||
public function filterListByAccess(array $projects, $user_id)
|
||||
{
|
||||
foreach ($projects as $project_id => $project_name) {
|
||||
if (! $this->isUserAllowed($project_id, $user_id)) {
|
||||
unset($projects[$project_id]);
|
||||
}
|
||||
}
|
||||
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of projects for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableList($user_id)
|
||||
{
|
||||
return $this->filterListByAccess($this->getListByStatus(self::ACTIVE), $user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a project
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return integer Project id
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
$values['token'] = Security::generateToken();
|
||||
|
||||
if (! $this->db->table(self::TABLE)->save($values)) {
|
||||
$this->db->cancelTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
$project_id = $this->db->getConnection()->getLastId();
|
||||
|
||||
$boardModel = new Board($this->db, $this->event);
|
||||
$boardModel->create($project_id, array(
|
||||
t('Backlog'),
|
||||
t('Ready'),
|
||||
t('Work in progress'),
|
||||
t('Done'),
|
||||
));
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return (int) $project_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the project have been modified
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $timestamp Timestamp
|
||||
* @return bool
|
||||
*/
|
||||
public function isModifiedSince($project_id, $timestamp)
|
||||
{
|
||||
return (bool) $this->db->table(self::TABLE)
|
||||
->eq('id', $project_id)
|
||||
->gt('last_modified', $timestamp)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update modification date
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return bool
|
||||
*/
|
||||
public function updateModificationDate($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $project_id)->save(array(
|
||||
'last_modified' => time()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a project
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($project_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $project_id)->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return bool
|
||||
*/
|
||||
public function enable($project_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $project_id)
|
||||
->save(array('is_active' => 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a project
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return bool
|
||||
*/
|
||||
public function disable($project_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $project_id)
|
||||
->save(array('is_active' => 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate project creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('name', t('The project name is required')),
|
||||
new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50),
|
||||
new Validators\Unique('name', t('This project must be unique'), $this->db->getConnection(), self::TABLE)
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate project modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateModification(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('The project id is required')),
|
||||
new Validators\Integer('id', t('This value must be an integer')),
|
||||
new Validators\Required('name', t('The project name is required')),
|
||||
new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50),
|
||||
new Validators\Unique('name', t('This project must be unique'), $this->db->getConnection(), self::TABLE),
|
||||
new Validators\Integer('is_active', t('This value must be an integer'))
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate allowed users
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateUserAccess(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('project_id', t('The project id is required')),
|
||||
new Validators\Integer('project_id', t('This value must be an integer')),
|
||||
new Validators\Required('user_id', t('The user id is required')),
|
||||
new Validators\Integer('user_id', t('This value must be an integer')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach events
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function attachEvents()
|
||||
{
|
||||
$events = array(
|
||||
Task::EVENT_UPDATE,
|
||||
Task::EVENT_CREATE,
|
||||
Task::EVENT_CLOSE,
|
||||
Task::EVENT_OPEN,
|
||||
);
|
||||
|
||||
$listener = new TaskModification($this);
|
||||
|
||||
foreach ($events as $event_name) {
|
||||
$this->event->attach($event_name, $listener);
|
||||
}
|
||||
}
|
||||
}
|
337
sources/app/Model/RememberMe.php
Normal file
337
sources/app/Model/RememberMe.php
Normal file
|
@ -0,0 +1,337 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* RememberMe model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class RememberMe extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'remember_me';
|
||||
|
||||
/**
|
||||
* Cookie name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COOKIE_NAME = '__R';
|
||||
|
||||
/**
|
||||
* Expiration (60 days)
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const EXPIRATION = 5184000;
|
||||
|
||||
/**
|
||||
* Get a remember me record
|
||||
*
|
||||
* @access public
|
||||
* @param $token
|
||||
* @param $sequence
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($token, $sequence)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('token', $token)
|
||||
->eq('sequence', $sequence)
|
||||
->gt('expiration', time())
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sessions for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->desc('date_creation')
|
||||
->columns('id', 'ip', 'user_agent', 'date_creation', 'expiration')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user with the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticate()
|
||||
{
|
||||
$credentials = $this->readCookie();
|
||||
|
||||
if ($credentials !== false) {
|
||||
|
||||
$record = $this->find($credentials['token'], $credentials['sequence']);
|
||||
|
||||
if ($record) {
|
||||
|
||||
// Update the sequence
|
||||
$this->writeCookie(
|
||||
$record['token'],
|
||||
$this->update($record['token'], $record['sequence']),
|
||||
$record['expiration']
|
||||
);
|
||||
|
||||
// Create the session
|
||||
$user = new User($this->db, $this->event);
|
||||
$acl = new Acl($this->db, $this->event);
|
||||
|
||||
$user->updateSession($user->getById($record['user_id']));
|
||||
$acl->isRememberMe(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the database and the cookie with a new sequence
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
$credentials = $this->readCookie();
|
||||
|
||||
if ($credentials !== false) {
|
||||
|
||||
$record = $this->find($credentials['token'], $credentials['sequence']);
|
||||
|
||||
if ($record) {
|
||||
|
||||
// Update the sequence
|
||||
$this->writeCookie(
|
||||
$record['token'],
|
||||
$this->update($record['token'], $record['sequence']),
|
||||
$record['expiration']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a session record
|
||||
*
|
||||
* @access public
|
||||
* @param integer $session_id Session id
|
||||
* @return mixed
|
||||
*/
|
||||
public function remove($session_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $session_id)
|
||||
->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the current RememberMe session and the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
*/
|
||||
public function destroy($user_id)
|
||||
{
|
||||
$credentials = $this->readCookie();
|
||||
|
||||
if ($credentials !== false) {
|
||||
|
||||
$this->deleteCookie();
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->eq('token', $credentials['token'])
|
||||
->remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RememberMe session
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @param string $ip IP Address
|
||||
* @param string $user_agent User Agent
|
||||
* @return array
|
||||
*/
|
||||
public function create($user_id, $ip, $user_agent)
|
||||
{
|
||||
$token = hash('sha256', $user_id.$user_agent.$ip.Security::generateToken());
|
||||
$sequence = Security::generateToken();
|
||||
$expiration = time() + self::EXPIRATION;
|
||||
|
||||
$this->cleanup($user_id);
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
->insert(array(
|
||||
'user_id' => $user_id,
|
||||
'ip' => $ip,
|
||||
'user_agent' => $user_agent,
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
'expiration' => $expiration,
|
||||
'date_creation' => time(),
|
||||
));
|
||||
|
||||
return array(
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
'expiration' => $expiration,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old sessions for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return bool
|
||||
*/
|
||||
public function cleanup($user_id)
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->lt('expiration', time())
|
||||
->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new sequence token and update the database
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @return string
|
||||
*/
|
||||
public function update($token, $sequence)
|
||||
{
|
||||
$new_sequence = Security::generateToken();
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
->eq('token', $token)
|
||||
->eq('sequence', $sequence)
|
||||
->update(array('sequence' => $new_sequence));
|
||||
|
||||
return $new_sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @return string
|
||||
*/
|
||||
public function encodeCookie($token, $sequence)
|
||||
{
|
||||
return implode('|', array($token, $sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the value of a cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Raw cookie data
|
||||
* @return array
|
||||
*/
|
||||
public function decodeCookie($value)
|
||||
{
|
||||
list($token, $sequence) = explode('|', $value);
|
||||
|
||||
return array(
|
||||
'token' => $token,
|
||||
'sequence' => $sequence,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the current user has a RememberMe cookie
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCookie()
|
||||
{
|
||||
return ! empty($_COOKIE[self::COOKIE_NAME]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write and encode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @param string $token Session token
|
||||
* @param string $sequence Sequence token
|
||||
* @param string $expiration Cookie expiration
|
||||
*/
|
||||
public function writeCookie($token, $sequence, $expiration)
|
||||
{
|
||||
setcookie(
|
||||
self::COOKIE_NAME,
|
||||
$this->encodeCookie($token, $sequence),
|
||||
$expiration,
|
||||
BASE_URL_DIRECTORY,
|
||||
null,
|
||||
! empty($_SERVER['HTTPS']),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and decode the cookie
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function readCookie()
|
||||
{
|
||||
if (empty($_COOKIE[self::COOKIE_NAME])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->decodeCookie($_COOKIE[self::COOKIE_NAME]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the cookie
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function deleteCookie()
|
||||
{
|
||||
setcookie(
|
||||
self::COOKIE_NAME,
|
||||
'',
|
||||
time() - 3600,
|
||||
BASE_URL_DIRECTORY,
|
||||
null,
|
||||
! empty($_SERVER['HTTPS']),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
179
sources/app/Model/SubTask.php
Normal file
179
sources/app/Model/SubTask.php
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
|
||||
/**
|
||||
* Subtask model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class SubTask extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'task_has_subtasks';
|
||||
|
||||
/**
|
||||
* Task "done" status
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const STATUS_DONE = 2;
|
||||
|
||||
/**
|
||||
* Task "in progress" status
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const STATUS_INPROGRESS = 1;
|
||||
|
||||
/**
|
||||
* Task "todo" status
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const STATUS_TODO = 0;
|
||||
|
||||
/**
|
||||
* Get available status
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getStatusList()
|
||||
{
|
||||
$status = array(
|
||||
self::STATUS_TODO => t('Todo'),
|
||||
self::STATUS_INPROGRESS => t('In progress'),
|
||||
self::STATUS_DONE => t('Done'),
|
||||
);
|
||||
|
||||
asort($status);
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all subtasks for a given task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($task_id)
|
||||
{
|
||||
$status = $this->getStatusList();
|
||||
$subtasks = $this->db->table(self::TABLE)
|
||||
->eq('task_id', $task_id)
|
||||
->columns(self::TABLE.'.*', User::TABLE.'.username')
|
||||
->join(User::TABLE, 'id', 'user_id')
|
||||
->findAll();
|
||||
|
||||
foreach ($subtasks as &$subtask) {
|
||||
$subtask['status_name'] = $status[$subtask['status']];
|
||||
}
|
||||
|
||||
return $subtasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a subtask by the id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $subtask_id Subtask id
|
||||
* @return array
|
||||
*/
|
||||
public function getById($subtask_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $subtask_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
if (isset($values['another_subtask'])) {
|
||||
unset($values['another_subtask']);
|
||||
}
|
||||
|
||||
if (isset($values['time_estimated']) && empty($values['time_estimated'])) {
|
||||
$values['time_estimated'] = 0;
|
||||
}
|
||||
|
||||
if (isset($values['time_spent']) && empty($values['time_spent'])) {
|
||||
$values['time_spent'] = 0;
|
||||
}
|
||||
|
||||
return $this->db->table(self::TABLE)->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
if (isset($values['time_estimated']) && empty($values['time_estimated'])) {
|
||||
$values['time_estimated'] = 0;
|
||||
}
|
||||
|
||||
if (isset($values['time_spent']) && empty($values['time_spent'])) {
|
||||
$values['time_spent'] = 0;
|
||||
}
|
||||
|
||||
return $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove
|
||||
*
|
||||
* @access public
|
||||
* @param integer $subtask_id Subtask id
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($subtask_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate creation/modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validate(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('task_id', t('The task id is required')),
|
||||
new Validators\Integer('task_id', t('The task id must be an integer')),
|
||||
new Validators\Required('title', t('The title is required')),
|
||||
new Validators\MaxLength('title', t('The maximum length is %d characters', 100), 100),
|
||||
new Validators\Integer('user_id', t('The user id must be an integer')),
|
||||
new Validators\Integer('status', t('The status must be an integer')),
|
||||
new Validators\Numeric('time_estimated', t('The time must be a numeric value')),
|
||||
new Validators\Numeric('time_spent', t('The time must be a numeric value')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
}
|
670
sources/app/Model/Task.php
Normal file
670
sources/app/Model/Task.php
Normal file
|
@ -0,0 +1,670 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Task extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'tasks';
|
||||
|
||||
/**
|
||||
* Task status
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const STATUS_OPEN = 1;
|
||||
const STATUS_CLOSED = 0;
|
||||
|
||||
/**
|
||||
* Events
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_MOVE_COLUMN = 'task.move.column';
|
||||
const EVENT_MOVE_POSITION = 'task.move.position';
|
||||
const EVENT_UPDATE = 'task.update';
|
||||
const EVENT_CREATE = 'task.create';
|
||||
const EVENT_CLOSE = 'task.close';
|
||||
const EVENT_OPEN = 'task.open';
|
||||
const EVENT_CREATE_UPDATE = 'task.create_update';
|
||||
|
||||
/**
|
||||
* Get available colors
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getColors()
|
||||
{
|
||||
return array(
|
||||
'yellow' => t('Yellow'),
|
||||
'blue' => t('Blue'),
|
||||
'green' => t('Green'),
|
||||
'purple' => t('Purple'),
|
||||
'red' => t('Red'),
|
||||
'orange' => t('Orange'),
|
||||
'grey' => t('Grey'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch one task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @param boolean $more If true, fetch all related information
|
||||
* @return array
|
||||
*/
|
||||
public function getById($task_id, $more = false)
|
||||
{
|
||||
if ($more) {
|
||||
|
||||
$sql = '
|
||||
SELECT
|
||||
tasks.id,
|
||||
tasks.title,
|
||||
tasks.description,
|
||||
tasks.date_creation,
|
||||
tasks.date_completed,
|
||||
tasks.date_modification,
|
||||
tasks.date_due,
|
||||
tasks.color_id,
|
||||
tasks.project_id,
|
||||
tasks.column_id,
|
||||
tasks.owner_id,
|
||||
tasks.creator_id,
|
||||
tasks.position,
|
||||
tasks.is_active,
|
||||
tasks.score,
|
||||
tasks.category_id,
|
||||
project_has_categories.name AS category_name,
|
||||
projects.name AS project_name,
|
||||
columns.title AS column_title,
|
||||
users.username AS assignee_username,
|
||||
creators.username AS creator_username
|
||||
FROM tasks
|
||||
LEFT JOIN users ON users.id = tasks.owner_id
|
||||
LEFT JOIN users AS creators ON creators.id = tasks.creator_id
|
||||
LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id
|
||||
LEFT JOIN projects ON projects.id = tasks.project_id
|
||||
LEFT JOIN columns ON columns.id = tasks.column_id
|
||||
WHERE tasks.id = ?
|
||||
';
|
||||
|
||||
$rq = $this->db->execute($sql, array($task_id));
|
||||
return $rq->fetch(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
else {
|
||||
|
||||
return $this->db->table(self::TABLE)->eq('id', $task_id)->findOne();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all tasks for a given project and status
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param array $status List of status id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED))
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('project_id', $project_id)
|
||||
->in('is_active', $status)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all tasks for a given project and status
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param array $status List of status id
|
||||
* @return integer
|
||||
*/
|
||||
public function countByProjectId($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED))
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('project_id', $project_id)
|
||||
->in('is_active', $status)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tasks that match defined filters
|
||||
*
|
||||
* @access public
|
||||
* @param array $filters Filters: [ ['column' => '...', 'operator' => '...', 'value' => '...'], ... ]
|
||||
* @param array $sorting Sorting: [ 'column' => 'date_creation', 'direction' => 'asc']
|
||||
* @return array
|
||||
*/
|
||||
public function find(array $filters, array $sorting = array())
|
||||
{
|
||||
$table = $this->db
|
||||
->table(self::TABLE)
|
||||
->columns(
|
||||
'(SELECT count(*) FROM comments WHERE task_id=tasks.id) AS nb_comments',
|
||||
'(SELECT count(*) FROM task_has_files WHERE task_id=tasks.id) AS nb_files',
|
||||
'tasks.id',
|
||||
'tasks.title',
|
||||
'tasks.description',
|
||||
'tasks.date_creation',
|
||||
'tasks.date_completed',
|
||||
'tasks.date_due',
|
||||
'tasks.color_id',
|
||||
'tasks.project_id',
|
||||
'tasks.column_id',
|
||||
'tasks.owner_id',
|
||||
'tasks.position',
|
||||
'tasks.is_active',
|
||||
'tasks.score',
|
||||
'tasks.category_id',
|
||||
'users.username'
|
||||
)
|
||||
->join('users', 'id', 'owner_id');
|
||||
|
||||
foreach ($filters as $key => $filter) {
|
||||
|
||||
if ($key === 'or') {
|
||||
|
||||
$table->beginOr();
|
||||
|
||||
foreach ($filter as $subfilter) {
|
||||
$table->$subfilter['operator']($subfilter['column'], $subfilter['value']);
|
||||
}
|
||||
|
||||
$table->closeOr();
|
||||
}
|
||||
else if (isset($filter['operator']) && isset($filter['column']) && isset($filter['value'])) {
|
||||
$table->$filter['operator']($filter['column'], $filter['value']);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($sorting)) {
|
||||
$table->orderBy('tasks.position', 'ASC');
|
||||
}
|
||||
else {
|
||||
$table->orderBy($sorting['column'], $sorting['direction']);
|
||||
}
|
||||
|
||||
return $table->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of tasks for a given column and status
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param integer $column_id Column id
|
||||
* @param array $status List of status id
|
||||
* @return integer
|
||||
*/
|
||||
public function countByColumnId($project_id, $column_id, array $status = array(self::STATUS_OPEN))
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('project_id', $project_id)
|
||||
->eq('column_id', $column_id)
|
||||
->in('is_active', $status)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate a task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return boolean
|
||||
*/
|
||||
public function duplicate($task_id)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
// Get the original task
|
||||
$task = $this->getById($task_id);
|
||||
|
||||
// Cleanup data
|
||||
unset($task['id']);
|
||||
unset($task['date_completed']);
|
||||
|
||||
// Assign new values
|
||||
$task['date_creation'] = time();
|
||||
$task['is_active'] = 1;
|
||||
$task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']);
|
||||
|
||||
// Save task
|
||||
if (! $this->db->table(self::TABLE)->save($task)) {
|
||||
$this->db->cancelTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
$task_id = $this->db->getConnection()->getLastId();
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
// Trigger events
|
||||
$this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $task);
|
||||
$this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task);
|
||||
|
||||
return $task_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate a task to another project (always copy to the first column)
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @param integer $project_id Destination project id
|
||||
* @return boolean
|
||||
*/
|
||||
public function duplicateToAnotherProject($task_id, $project_id)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
$boardModel = new Board($this->db, $this->event);
|
||||
|
||||
// Get the original task
|
||||
$task = $this->getById($task_id);
|
||||
|
||||
// Cleanup data
|
||||
unset($task['id']);
|
||||
unset($task['date_completed']);
|
||||
|
||||
// Assign new values
|
||||
$task['date_creation'] = time();
|
||||
$task['owner_id'] = 0;
|
||||
$task['category_id'] = 0;
|
||||
$task['is_active'] = 1;
|
||||
$task['column_id'] = $boardModel->getFirstColumn($project_id);
|
||||
$task['project_id'] = $project_id;
|
||||
$task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']);
|
||||
|
||||
// Save task
|
||||
if (! $this->db->table(self::TABLE)->save($task)) {
|
||||
$this->db->cancelTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
$task_id = $this->db->getConnection()->getLastId();
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
// Trigger events
|
||||
$this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $task);
|
||||
$this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task);
|
||||
|
||||
return $task_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a task
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return boolean
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
// Prepare data
|
||||
if (isset($values['another_task'])) {
|
||||
unset($values['another_task']);
|
||||
}
|
||||
|
||||
if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
|
||||
$values['date_due'] = $this->parseDate($values['date_due']);
|
||||
}
|
||||
else {
|
||||
$values['date_due'] = 0;
|
||||
}
|
||||
|
||||
if (empty($values['score'])) {
|
||||
$values['score'] = 0;
|
||||
}
|
||||
|
||||
$values['date_creation'] = time();
|
||||
$values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']);
|
||||
|
||||
// Save task
|
||||
if (! $this->db->table(self::TABLE)->save($values)) {
|
||||
$this->db->cancelTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
$task_id = $this->db->getConnection()->getLastId();
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
// Trigger events
|
||||
$this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $values);
|
||||
$this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $values);
|
||||
|
||||
return $task_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a task
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return boolean
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
// Prepare data
|
||||
if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
|
||||
$values['date_due'] = $this->parseDate($values['date_due']);
|
||||
}
|
||||
|
||||
// Force integer fields at 0 (for Postgresql)
|
||||
if (isset($values['date_due']) && empty($values['date_due'])) {
|
||||
$values['date_due'] = 0;
|
||||
}
|
||||
|
||||
if (isset($values['score']) && empty($values['score'])) {
|
||||
$values['score'] = 0;
|
||||
}
|
||||
|
||||
$original_task = $this->getById($values['id']);
|
||||
|
||||
if ($original_task === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updated_task = $values;
|
||||
$updated_task['date_modification'] = time();
|
||||
unset($updated_task['id']);
|
||||
|
||||
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updated_task);
|
||||
|
||||
// Trigger events
|
||||
if ($result) {
|
||||
|
||||
$events = array(
|
||||
self::EVENT_CREATE_UPDATE,
|
||||
self::EVENT_UPDATE,
|
||||
);
|
||||
|
||||
if (isset($values['column_id']) && $original_task['column_id'] != $values['column_id']) {
|
||||
$events[] = self::EVENT_MOVE_COLUMN;
|
||||
}
|
||||
else if (isset($values['position']) && $original_task['position'] != $values['position']) {
|
||||
$events[] = self::EVENT_MOVE_POSITION;
|
||||
}
|
||||
|
||||
$event_data = array_merge($original_task, $values);
|
||||
$event_data['task_id'] = $original_task['id'];
|
||||
|
||||
foreach ($events as $event) {
|
||||
$this->event->trigger($event, $event_data);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a task closed
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return boolean
|
||||
*/
|
||||
public function close($task_id)
|
||||
{
|
||||
$result = $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $task_id)
|
||||
->update(array(
|
||||
'is_active' => 0,
|
||||
'date_completed' => time()
|
||||
));
|
||||
|
||||
if ($result) {
|
||||
$this->event->trigger(self::EVENT_CLOSE, array('task_id' => $task_id) + $this->getById($task_id));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a task open
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return boolean
|
||||
*/
|
||||
public function open($task_id)
|
||||
{
|
||||
$result = $this->db
|
||||
->table(self::TABLE)
|
||||
->eq('id', $task_id)
|
||||
->update(array(
|
||||
'is_active' => 1,
|
||||
'date_completed' => ''
|
||||
));
|
||||
|
||||
if ($result) {
|
||||
$this->event->trigger(self::EVENT_OPEN, array('task_id' => $task_id) + $this->getById($task_id));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return boolean
|
||||
*/
|
||||
public function remove($task_id)
|
||||
{
|
||||
$file = new File($this->db, $this->event);
|
||||
$file->removeAll($task_id);
|
||||
|
||||
return $this->db->table(self::TABLE)->eq('id', $task_id)->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a task to another column or to another position
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @param integer $column_id Column id
|
||||
* @param integer $position Position (must be greater than 1)
|
||||
* @return boolean
|
||||
*/
|
||||
public function move($task_id, $column_id, $position)
|
||||
{
|
||||
$this->event->clearTriggeredEvents();
|
||||
|
||||
return $this->update(array(
|
||||
'id' => $task_id,
|
||||
'column_id' => $column_id,
|
||||
'position' => $position,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate task creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('color_id', t('The color is required')),
|
||||
new Validators\Required('project_id', t('The project is required')),
|
||||
new Validators\Integer('project_id', t('This value must be an integer')),
|
||||
new Validators\Required('column_id', t('The column is required')),
|
||||
new Validators\Integer('column_id', t('This value must be an integer')),
|
||||
new Validators\Integer('owner_id', t('This value must be an integer')),
|
||||
new Validators\Integer('creator_id', t('This value must be an integer')),
|
||||
new Validators\Integer('score', t('This value must be an integer')),
|
||||
new Validators\Required('title', t('The title is required')),
|
||||
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
|
||||
new Validators\Date('date_due', t('Invalid date'), $this->getDateFormats()),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate description creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateDescriptionCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('The id is required')),
|
||||
new Validators\Integer('id', t('This value must be an integer')),
|
||||
new Validators\Required('description', t('The description is required')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate task modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateModification(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('The id is required')),
|
||||
new Validators\Integer('id', t('This value must be an integer')),
|
||||
new Validators\Required('color_id', t('The color is required')),
|
||||
new Validators\Required('project_id', t('The project is required')),
|
||||
new Validators\Integer('project_id', t('This value must be an integer')),
|
||||
new Validators\Required('column_id', t('The column is required')),
|
||||
new Validators\Integer('column_id', t('This value must be an integer')),
|
||||
new Validators\Integer('owner_id', t('This value must be an integer')),
|
||||
new Validators\Integer('score', t('This value must be an integer')),
|
||||
new Validators\Required('title', t('The title is required')),
|
||||
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
|
||||
new Validators\Date('date_due', t('Invalid date'), $this->getDateFormats()),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate assignee change
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateAssigneeModification(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('The id is required')),
|
||||
new Validators\Integer('id', t('This value must be an integer')),
|
||||
new Validators\Required('project_id', t('The project is required')),
|
||||
new Validators\Integer('project_id', t('This value must be an integer')),
|
||||
new Validators\Required('owner_id', t('This value is required')),
|
||||
new Validators\Integer('owner_id', t('This value must be an integer')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a timestamp if the given date format is correct otherwise return 0
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Date to parse
|
||||
* @param string $format Date format
|
||||
* @return integer
|
||||
*/
|
||||
public function getValidDate($value, $format)
|
||||
{
|
||||
$date = DateTime::createFromFormat($format, $value);
|
||||
|
||||
if ($date !== false) {
|
||||
$errors = DateTime::getLastErrors();
|
||||
if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
|
||||
$timestamp = $date->getTimestamp();
|
||||
return $timestamp > 0 ? $timestamp : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a date ad return a unix timestamp, try different date formats
|
||||
*
|
||||
* @access public
|
||||
* @param string $value Date to parse
|
||||
* @return integer
|
||||
*/
|
||||
public function parseDate($value)
|
||||
{
|
||||
foreach ($this->getDateFormats() as $format) {
|
||||
|
||||
$timestamp = $this->getValidDate($value, $format);
|
||||
|
||||
if ($timestamp !== 0) {
|
||||
return $timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of supported date formats
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getDateFormats()
|
||||
{
|
||||
return array(
|
||||
t('m/d/Y'),
|
||||
'Y-m-d',
|
||||
'Y_m_d',
|
||||
);
|
||||
}
|
||||
}
|
450
sources/app/Model/User.php
Normal file
450
sources/app/Model/User.php
Normal file
|
@ -0,0 +1,450 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
|
||||
/**
|
||||
* User model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class User extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'users';
|
||||
|
||||
/**
|
||||
* Id used for everbody (filtering)
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const EVERYBODY_ID = -1;
|
||||
|
||||
/**
|
||||
* Get a specific user by id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return array
|
||||
*/
|
||||
public function getById($user_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $user_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific user by the Google id
|
||||
*
|
||||
* @access public
|
||||
* @param string $google_id Google unique id
|
||||
* @return array
|
||||
*/
|
||||
public function getByGoogleId($google_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('google_id', $google_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific user by the GitHub id
|
||||
*
|
||||
* @access public
|
||||
* @param string $github_id GitHub user id
|
||||
* @return array
|
||||
*/
|
||||
public function getByGitHubId($github_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('github_id', $github_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific user by the username
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @return array
|
||||
*/
|
||||
public function getByUsername($username)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('username', $username)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return $this->db
|
||||
->table(self::TABLE)
|
||||
->asc('username')
|
||||
->columns('id', 'username', 'name', 'email', 'is_admin', 'default_project_id', 'is_ldap_user')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all users (key-value pairs with id/username)
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
return $this->db->table(self::TABLE)->asc('username')->listing('id', 'username');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new user in the database
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return boolean
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
if (isset($values['confirmation'])) {
|
||||
unset($values['confirmation']);
|
||||
}
|
||||
|
||||
if (isset($values['password'])) {
|
||||
$values['password'] = \password_hash($values['password'], PASSWORD_BCRYPT);
|
||||
}
|
||||
|
||||
if (empty($values['is_admin'])) {
|
||||
$values['is_admin'] = 0;
|
||||
}
|
||||
|
||||
if (empty($values['is_ldap_user'])) {
|
||||
$values['is_ldap_user'] = 0;
|
||||
}
|
||||
|
||||
return $this->db->table(self::TABLE)->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a new user
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
if (! empty($values['password'])) {
|
||||
$values['password'] = \password_hash($values['password'], PASSWORD_BCRYPT);
|
||||
}
|
||||
else {
|
||||
unset($values['password']);
|
||||
}
|
||||
|
||||
if (isset($values['confirmation'])) {
|
||||
unset($values['confirmation']);
|
||||
}
|
||||
|
||||
if (isset($values['current_password'])) {
|
||||
unset($values['current_password']);
|
||||
}
|
||||
|
||||
if (empty($values['is_admin'])) {
|
||||
$values['is_admin'] = 0;
|
||||
}
|
||||
|
||||
if (empty($values['is_ldap_user'])) {
|
||||
$values['is_ldap_user'] = 0;
|
||||
}
|
||||
|
||||
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
|
||||
|
||||
if (session_id() !== '' && $_SESSION['user']['id'] == $values['id']) {
|
||||
$this->updateSession();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function remove($user_id)
|
||||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
// All tasks assigned to this user will be unassigned
|
||||
$this->db->table(Task::TABLE)->eq('owner_id', $user_id)->update(array('owner_id' => ''));
|
||||
$this->db->table(self::TABLE)->eq('id', $user_id)->remove();
|
||||
|
||||
$this->db->closeTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user session information
|
||||
*
|
||||
* @access public
|
||||
* @param array $user User data
|
||||
*/
|
||||
public function updateSession(array $user = array())
|
||||
{
|
||||
if (empty($user)) {
|
||||
$user = $this->getById($_SESSION['user']['id']);
|
||||
}
|
||||
|
||||
if (isset($user['password'])) {
|
||||
unset($user['password']);
|
||||
}
|
||||
|
||||
$user['id'] = (int) $user['id'];
|
||||
$user['default_project_id'] = (int) $user['default_project_id'];
|
||||
$user['is_admin'] = (bool) $user['is_admin'];
|
||||
$user['is_ldap_user'] = (bool) $user['is_ldap_user'];
|
||||
|
||||
$_SESSION['user'] = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate user creation
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateCreation(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('username', t('The username is required')),
|
||||
new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50),
|
||||
new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), self::TABLE, 'id'),
|
||||
new Validators\Required('password', t('The password is required')),
|
||||
new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6),
|
||||
new Validators\Required('confirmation', t('The confirmation is required')),
|
||||
new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')),
|
||||
new Validators\Integer('default_project_id', t('This value must be an integer')),
|
||||
new Validators\Integer('is_admin', t('This value must be an integer')),
|
||||
new Validators\Email('email', t('Email address invalid')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate user modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateModification(array $values)
|
||||
{
|
||||
if (! empty($values['password'])) {
|
||||
return $this->validatePasswordModification($values);
|
||||
}
|
||||
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('The user id is required')),
|
||||
new Validators\Required('username', t('The username is required')),
|
||||
new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50),
|
||||
new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), self::TABLE, 'id'),
|
||||
new Validators\Integer('default_project_id', t('This value must be an integer')),
|
||||
new Validators\Integer('is_admin', t('This value must be an integer')),
|
||||
new Validators\Email('email', t('Email address invalid')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate password modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validatePasswordModification(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('id', t('The user id is required')),
|
||||
new Validators\Required('username', t('The username is required')),
|
||||
new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50),
|
||||
new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), self::TABLE, 'id'),
|
||||
new Validators\Required('current_password', t('The current password is required')),
|
||||
new Validators\Required('password', t('The password is required')),
|
||||
new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6),
|
||||
new Validators\Required('confirmation', t('The confirmation is required')),
|
||||
new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')),
|
||||
new Validators\Integer('default_project_id', t('This value must be an integer')),
|
||||
new Validators\Integer('is_admin', t('This value must be an integer')),
|
||||
new Validators\Email('email', t('Email address invalid')),
|
||||
));
|
||||
|
||||
if ($v->execute()) {
|
||||
|
||||
// Check password
|
||||
list($authenticated,) = $this->authenticate($_SESSION['user']['username'], $values['current_password']);
|
||||
|
||||
if ($authenticated) {
|
||||
return array(true, array());
|
||||
}
|
||||
else {
|
||||
return array(false, array('current_password' => array(t('Wrong password'))));
|
||||
}
|
||||
}
|
||||
|
||||
return array(false, $v->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate user login
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validateLogin(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('username', t('The username is required')),
|
||||
new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50),
|
||||
new Validators\Required('password', t('The password is required')),
|
||||
));
|
||||
|
||||
$result = $v->execute();
|
||||
$errors = $v->getErrors();
|
||||
|
||||
if ($result) {
|
||||
|
||||
list($authenticated, $method) = $this->authenticate($values['username'], $values['password']);
|
||||
|
||||
if ($authenticated === true) {
|
||||
|
||||
// Create the user session
|
||||
$user = $this->getByUsername($values['username']);
|
||||
$this->updateSession($user);
|
||||
|
||||
// Update login history
|
||||
$lastLogin = new LastLogin($this->db, $this->event);
|
||||
$lastLogin->create(
|
||||
$method,
|
||||
$user['id'],
|
||||
$this->getIpAddress(),
|
||||
$this->getUserAgent()
|
||||
);
|
||||
|
||||
// Setup the remember me feature
|
||||
if (! empty($values['remember_me'])) {
|
||||
$rememberMe = new RememberMe($this->db, $this->event);
|
||||
$credentials = $rememberMe->create($user['id'], $this->getIpAddress(), $this->getUserAgent());
|
||||
$rememberMe->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result = false;
|
||||
$errors['login'] = t('Bad username or password');
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$result,
|
||||
$errors
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user
|
||||
*
|
||||
* @access public
|
||||
* @param string $username Username
|
||||
* @param string $password Password
|
||||
* @return array
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
// Database authentication
|
||||
$user = $this->db->table(self::TABLE)->eq('username', $username)->eq('is_ldap_user', 0)->findOne();
|
||||
$authenticated = $user && \password_verify($password, $user['password']);
|
||||
$method = LastLogin::AUTH_DATABASE;
|
||||
|
||||
// LDAP authentication
|
||||
if (! $authenticated && LDAP_AUTH) {
|
||||
$ldap = new Ldap($this->db, $this->event);
|
||||
$authenticated = $ldap->authenticate($username, $password);
|
||||
$method = LastLogin::AUTH_LDAP;
|
||||
}
|
||||
|
||||
return array($authenticated, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user agent of the connected user
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getUserAgent()
|
||||
{
|
||||
return empty($_SERVER['HTTP_USER_AGENT']) ? t('Unknown') : $_SERVER['HTTP_USER_AGENT'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the real IP address of the connected user
|
||||
*
|
||||
* @access public
|
||||
* @param bool $only_public Return only public IP address
|
||||
* @return string
|
||||
*/
|
||||
public function getIpAddress($only_public = false)
|
||||
{
|
||||
$keys = array(
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_FORWARDED',
|
||||
'REMOTE_ADDR'
|
||||
);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
|
||||
if (isset($_SERVER[$key])) {
|
||||
|
||||
foreach (explode(',', $_SERVER[$key]) as $ip_address) {
|
||||
|
||||
$ip_address = trim($ip_address);
|
||||
|
||||
if ($only_public) {
|
||||
|
||||
// Return only public IP address
|
||||
if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
|
||||
return $ip_address;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
return $ip_address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t('Unknown');
|
||||
}
|
||||
}
|
286
sources/app/Schema/Mysql.php
Normal file
286
sources/app/Schema/Mysql.php
Normal file
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
namespace Schema;
|
||||
|
||||
use Core\Security;
|
||||
|
||||
const VERSION = 21;
|
||||
|
||||
function version_21($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN creator_id INTEGER DEFAULT '0'");
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_modification INTEGER DEFAULT '0'");
|
||||
}
|
||||
|
||||
function version_20($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN github_id VARCHAR(30)");
|
||||
}
|
||||
|
||||
function version_19($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE config ADD COLUMN api_token VARCHAR(255) DEFAULT '".Security::generateToken()."'");
|
||||
}
|
||||
|
||||
function version_18($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE task_has_subtasks (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
title VARCHAR(255),
|
||||
status INT DEFAULT 0,
|
||||
time_estimated INT DEFAULT 0,
|
||||
time_spent INT DEFAULT 0,
|
||||
task_id INT,
|
||||
user_id INT,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8"
|
||||
);
|
||||
}
|
||||
|
||||
function version_17($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE task_has_files (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(50),
|
||||
path VARCHAR(255),
|
||||
is_image TINYINT(1) DEFAULT 0,
|
||||
task_id INT,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8"
|
||||
);
|
||||
}
|
||||
|
||||
function version_16($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE project_has_categories (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(255),
|
||||
project_id INT,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY `idx_project_category` (project_id, name),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8"
|
||||
);
|
||||
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN category_id INT DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_15($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INT DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_14($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN name VARCHAR(255)");
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN google_id VARCHAR(30)");
|
||||
}
|
||||
|
||||
function version_13($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN is_ldap_user TINYINT(1) DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_12($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE remember_me (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
user_id INT,
|
||||
ip VARCHAR(40),
|
||||
user_agent VARCHAR(255),
|
||||
token VARCHAR(255),
|
||||
sequence VARCHAR(255),
|
||||
expiration INT,
|
||||
date_creation INT,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB CHARSET=utf8"
|
||||
);
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE last_logins (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
auth_type VARCHAR(25),
|
||||
user_id INT,
|
||||
ip VARCHAR(40),
|
||||
user_agent VARCHAR(255),
|
||||
date_creation INT,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (id),
|
||||
INDEX (user_id)
|
||||
) ENGINE=InnoDB CHARSET=utf8"
|
||||
);
|
||||
}
|
||||
|
||||
function version_11($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_10($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_9($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_8($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_7($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_6($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_5($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_4($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_3($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_2($pdo)
|
||||
{
|
||||
}
|
||||
|
||||
function version_1($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE config (
|
||||
language CHAR(5) DEFAULT 'en_US',
|
||||
webhooks_token VARCHAR(255),
|
||||
timezone VARCHAR(50) DEFAULT 'UTC'
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE users (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
username VARCHAR(50),
|
||||
password VARCHAR(255),
|
||||
is_admin TINYINT DEFAULT 0,
|
||||
default_project_id INT DEFAULT 0,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE projects (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(50) UNIQUE,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
token VARCHAR(255),
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE project_has_users (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
project_id INT,
|
||||
user_id INT,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY `idx_project_user` (project_id, user_id),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE columns (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
title VARCHAR(255),
|
||||
position INT NOT NULL,
|
||||
project_id INT NOT NULL,
|
||||
task_limit INT DEFAULT '0',
|
||||
UNIQUE KEY `idx_title_project` (title, project_id),
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE tasks (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
title VARCHAR(255),
|
||||
description TEXT,
|
||||
date_creation INT,
|
||||
date_completed INT,
|
||||
date_due INT,
|
||||
color_id VARCHAR(50),
|
||||
project_id INT,
|
||||
column_id INT,
|
||||
owner_id INT DEFAULT '0',
|
||||
position INT,
|
||||
score INT,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
PRIMARY KEY (id),
|
||||
INDEX `idx_task_active` (is_active),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE comments (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
task_id INT,
|
||||
user_id INT,
|
||||
date INT,
|
||||
comment TEXT,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE actions (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
project_id INT,
|
||||
event_name VARCHAR(50),
|
||||
action_name VARCHAR(50),
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE action_has_params (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
action_id INT,
|
||||
name VARCHAR(50),
|
||||
value VARCHAR(50),
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO users
|
||||
(username, password, is_admin)
|
||||
VALUES ('admin', '".\password_hash('admin', PASSWORD_BCRYPT)."', '1')
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO config
|
||||
(webhooks_token)
|
||||
VALUES ('".Security::generateToken()."')
|
||||
");
|
||||
}
|
172
sources/app/Schema/Postgres.php
Normal file
172
sources/app/Schema/Postgres.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace Schema;
|
||||
|
||||
use Core\Security;
|
||||
|
||||
const VERSION = 2;
|
||||
|
||||
function version_2($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN creator_id INTEGER DEFAULT 0");
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_modification INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_1($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE config (
|
||||
language CHAR(5) DEFAULT 'en_US',
|
||||
webhooks_token VARCHAR(255),
|
||||
timezone VARCHAR(50) DEFAULT 'UTC',
|
||||
api_token VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(50),
|
||||
password VARCHAR(255),
|
||||
is_admin BOOLEAN DEFAULT '0',
|
||||
default_project_id INTEGER DEFAULT 0,
|
||||
is_ldap_user BOOLEAN DEFAULT '0',
|
||||
name VARCHAR(255),
|
||||
email VARCHAR(255),
|
||||
google_id VARCHAR(255),
|
||||
github_id VARCHAR(30)
|
||||
);
|
||||
|
||||
CREATE TABLE remember_me (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
ip VARCHAR(40),
|
||||
user_agent VARCHAR(255),
|
||||
token VARCHAR(255),
|
||||
sequence VARCHAR(255),
|
||||
expiration INTEGER,
|
||||
date_creation INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE last_logins (
|
||||
id SERIAL PRIMARY KEY,
|
||||
auth_type VARCHAR(25),
|
||||
user_id INTEGER,
|
||||
ip VARCHAR(40),
|
||||
user_agent VARCHAR(255),
|
||||
date_creation INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) UNIQUE,
|
||||
is_active BOOLEAN DEFAULT '1',
|
||||
token VARCHAR(255),
|
||||
last_modified INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE project_has_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE(project_id, user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE project_has_categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
project_id INTEGER,
|
||||
UNIQUE (project_id, name),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE columns (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255),
|
||||
position INTEGER,
|
||||
project_id INTEGER,
|
||||
task_limit INTEGER DEFAULT 0,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
UNIQUE (title, project_id)
|
||||
);
|
||||
|
||||
CREATE TABLE tasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255),
|
||||
description TEXT,
|
||||
date_creation INTEGER,
|
||||
color_id VARCHAR(255),
|
||||
project_id INTEGER,
|
||||
column_id INTEGER,
|
||||
owner_id INTEGER DEFAULT 0,
|
||||
position INTEGER,
|
||||
is_active BOOLEAN DEFAULT '1',
|
||||
date_completed INTEGER,
|
||||
score INTEGER,
|
||||
date_due INTEGER,
|
||||
category_id INTEGER DEFAULT 0,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE task_has_subtasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255),
|
||||
status SMALLINT DEFAULT 0,
|
||||
time_estimated INTEGER DEFAULT 0,
|
||||
time_spent INTEGER DEFAULT 0,
|
||||
task_id INTEGER,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE task_has_files (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
path VARCHAR(255),
|
||||
is_image BOOLEAN DEFAULT '0',
|
||||
task_id INTEGER,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE comments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
task_id INTEGER,
|
||||
user_id INTEGER,
|
||||
date INTEGER,
|
||||
comment TEXT,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE actions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER,
|
||||
event_name VARCHAR(50),
|
||||
action_name VARCHAR(50),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE action_has_params (
|
||||
id SERIAL PRIMARY KEY,
|
||||
action_id INTEGER,
|
||||
name VARCHAR(50),
|
||||
value VARCHAR(50),
|
||||
FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO users
|
||||
(username, password, is_admin)
|
||||
VALUES ('admin', '".\password_hash('admin', PASSWORD_BCRYPT)."', '1')
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO config
|
||||
(webhooks_token, api_token)
|
||||
VALUES ('".Security::generateToken()."', '".Security::generateToken()."')
|
||||
");
|
||||
}
|
307
sources/app/Schema/Sqlite.php
Normal file
307
sources/app/Schema/Sqlite.php
Normal file
|
@ -0,0 +1,307 @@
|
|||
<?php
|
||||
|
||||
namespace Schema;
|
||||
|
||||
use Core\Security;
|
||||
|
||||
const VERSION = 21;
|
||||
|
||||
function version_21($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN creator_id INTEGER DEFAULT '0'");
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_modification INTEGER DEFAULT '0'");
|
||||
}
|
||||
|
||||
function version_20($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN github_id TEXT");
|
||||
}
|
||||
|
||||
function version_19($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE config ADD COLUMN api_token TEXT DEFAULT '".Security::generateToken()."'");
|
||||
}
|
||||
|
||||
function version_18($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE task_has_subtasks (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT COLLATE NOCASE,
|
||||
status INTEGER DEFAULT 0,
|
||||
time_estimated INTEGER DEFAULT 0,
|
||||
time_spent INTEGER DEFAULT 0,
|
||||
task_id INTEGER,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
)"
|
||||
);
|
||||
}
|
||||
|
||||
function version_17($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE task_has_files (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT COLLATE NOCASE,
|
||||
path TEXT,
|
||||
is_image INTEGER DEFAULT 0,
|
||||
task_id INTEGER,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
)"
|
||||
);
|
||||
}
|
||||
|
||||
function version_16($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE project_has_categories (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT COLLATE NOCASE,
|
||||
project_id INT,
|
||||
UNIQUE (project_id, name),
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||
)"
|
||||
);
|
||||
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN category_id INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_15($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_14($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN name TEXT");
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN email TEXT");
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN google_id TEXT");
|
||||
}
|
||||
|
||||
function version_13($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE users ADD COLUMN is_ldap_user INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
function version_12($pdo)
|
||||
{
|
||||
$pdo->exec(
|
||||
'CREATE TABLE remember_me (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
ip TEXT,
|
||||
user_agent TEXT,
|
||||
token TEXT,
|
||||
sequence TEXT,
|
||||
expiration INTEGER,
|
||||
date_creation INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'CREATE TABLE last_logins (
|
||||
id INTEGER PRIMARY KEY,
|
||||
auth_type TEXT,
|
||||
user_id INTEGER,
|
||||
ip TEXT,
|
||||
user_agent TEXT,
|
||||
date_creation INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)'
|
||||
);
|
||||
|
||||
$pdo->exec('CREATE INDEX last_logins_user_idx ON last_logins(user_id)');
|
||||
}
|
||||
|
||||
function version_11($pdo)
|
||||
{
|
||||
$pdo->exec(
|
||||
'ALTER TABLE comments RENAME TO comments_bak'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'CREATE TABLE comments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
task_id INTEGER,
|
||||
user_id INTEGER,
|
||||
date INTEGER,
|
||||
comment TEXT,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'INSERT INTO comments SELECT * FROM comments_bak'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'DROP TABLE comments_bak'
|
||||
);
|
||||
}
|
||||
|
||||
function version_10($pdo)
|
||||
{
|
||||
$pdo->exec(
|
||||
'CREATE TABLE actions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
project_id INTEGER,
|
||||
event_name TEXT,
|
||||
action_name TEXT,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||||
)'
|
||||
);
|
||||
|
||||
$pdo->exec(
|
||||
'CREATE TABLE action_has_params (
|
||||
id INTEGER PRIMARY KEY,
|
||||
action_id INTEGER,
|
||||
name TEXT,
|
||||
value TEXT,
|
||||
FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE
|
||||
)'
|
||||
);
|
||||
}
|
||||
|
||||
function version_9($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_due INTEGER");
|
||||
}
|
||||
|
||||
function version_8($pdo)
|
||||
{
|
||||
$pdo->exec(
|
||||
'CREATE TABLE comments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
task_id INTEGER,
|
||||
user_id INTEGER,
|
||||
date INTEGER,
|
||||
comment TEXT,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
)'
|
||||
);
|
||||
}
|
||||
|
||||
function version_7($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE project_has_users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
project_id INTEGER,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE(project_id, user_id)
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
function version_6($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE columns ADD COLUMN task_limit INTEGER DEFAULT '0'");
|
||||
}
|
||||
|
||||
function version_5($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE tasks ADD COLUMN score INTEGER");
|
||||
}
|
||||
|
||||
function version_4($pdo)
|
||||
{
|
||||
$pdo->exec("ALTER TABLE config ADD COLUMN timezone TEXT DEFAULT 'UTC'");
|
||||
}
|
||||
|
||||
function version_3($pdo)
|
||||
{
|
||||
$pdo->exec('ALTER TABLE projects ADD COLUMN token TEXT');
|
||||
|
||||
// For each existing project, assign a different token
|
||||
$rq = $pdo->prepare("SELECT id FROM projects WHERE token IS NULL");
|
||||
$rq->execute();
|
||||
$results = $rq->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
if ($results !== false) {
|
||||
|
||||
foreach ($results as &$result) {
|
||||
$rq = $pdo->prepare('UPDATE projects SET token=? WHERE id=?');
|
||||
$rq->execute(array(Security::generateToken(), $result['id']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function version_2($pdo)
|
||||
{
|
||||
$pdo->exec('ALTER TABLE tasks ADD COLUMN date_completed INTEGER');
|
||||
$pdo->exec('UPDATE tasks SET date_completed=date_creation WHERE is_active=0');
|
||||
}
|
||||
|
||||
function version_1($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE config (
|
||||
language TEXT,
|
||||
webhooks_token TEXT
|
||||
)
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT,
|
||||
password TEXT,
|
||||
is_admin INTEGER DEFAULT 0,
|
||||
default_project_id INTEGER DEFAULT 0
|
||||
)
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE projects (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOCASE UNIQUE,
|
||||
is_active INTEGER DEFAULT 1
|
||||
)
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE columns (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT,
|
||||
position INTEGER,
|
||||
project_id INTEGER,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
UNIQUE (title, project_id)
|
||||
)
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
CREATE TABLE tasks (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
date_creation INTEGER,
|
||||
color_id TEXT,
|
||||
project_id INTEGER,
|
||||
column_id INTEGER,
|
||||
owner_id INTEGER DEFAULT '0',
|
||||
position INTEGER,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE
|
||||
)
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO users
|
||||
(username, password, is_admin)
|
||||
VALUES ('admin', '".\password_hash('admin', PASSWORD_BCRYPT)."', '1')
|
||||
");
|
||||
|
||||
$pdo->exec("
|
||||
INSERT INTO config
|
||||
(language, webhooks_token)
|
||||
VALUES ('en_US', '".Security::generateToken()."')
|
||||
");
|
||||
}
|
77
sources/app/Templates/action_index.php
Normal file
77
sources/app/Templates/action_index.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=project"><?= t('All projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
|
||||
<?php if (! empty($actions)): ?>
|
||||
|
||||
<h3><?= t('Defined actions') ?></h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th><?= t('Event name') ?></th>
|
||||
<th><?= t('Action name') ?></th>
|
||||
<th><?= t('Action parameters') ?></th>
|
||||
<th><?= t('Action') ?></th>
|
||||
</tr>
|
||||
|
||||
<?php foreach ($actions as $action): ?>
|
||||
<tr>
|
||||
<td><?= Helper\in_list($action['event_name'], $available_events) ?></td>
|
||||
<td><?= Helper\in_list($action['action_name'], $available_actions) ?></td>
|
||||
<td>
|
||||
<ul>
|
||||
<?php foreach ($action['params'] as $param): ?>
|
||||
<li>
|
||||
<?= Helper\in_list($param['name'], $available_params) ?> =
|
||||
<strong>
|
||||
<?php if (Helper\contains($param['name'], 'column_id')): ?>
|
||||
<?= Helper\in_list($param['value'], $columns_list) ?>
|
||||
<?php elseif (Helper\contains($param['name'], 'user_id')): ?>
|
||||
<?= Helper\in_list($param['value'], $users_list) ?>
|
||||
<?php elseif (Helper\contains($param['name'], 'project_id')): ?>
|
||||
<?= Helper\in_list($param['value'], $projects_list) ?>
|
||||
<?php elseif (Helper\contains($param['name'], 'color_id')): ?>
|
||||
<?= Helper\in_list($param['value'], $colors_list) ?>
|
||||
<?php elseif (Helper\contains($param['name'], 'category_id')): ?>
|
||||
<?= Helper\in_list($param['value'], $categories_list) ?>
|
||||
<?php endif ?>
|
||||
</strong>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<a href="?controller=action&action=confirm&action_id=<?= $action['id'] ?>"><?= t('Remove') ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
|
||||
</table>
|
||||
|
||||
<?php endif ?>
|
||||
|
||||
<h3><?= t('Add an action') ?></h3>
|
||||
<form method="post" action="?controller=action&action=params&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Event'), 'event_name') ?>
|
||||
<?= Helper\form_select('event_name', $available_events, $values) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Action'), 'action_name') ?>
|
||||
<?= Helper\form_select('action_name', $available_actions, $values) ?><br/>
|
||||
|
||||
<div class="form-help">
|
||||
<?= t('When the selected event occurs execute the corresponding action.') ?>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Next step') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</section>
|
43
sources/app/Templates/action_params.php
Normal file
43
sources/app/Templates/action_params.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=project"><?= t('All projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
|
||||
<h3><?= t('Define action parameters') ?></h3>
|
||||
<form method="post" action="?controller=action&action=create&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
<?= Helper\form_hidden('event_name', $values) ?>
|
||||
<?= Helper\form_hidden('action_name', $values) ?>
|
||||
|
||||
<?php foreach ($action_params as $param_name => $param_desc): ?>
|
||||
|
||||
<?php if (Helper\contains($param_name, 'column_id')): ?>
|
||||
<?= Helper\form_label($param_desc, $param_name) ?>
|
||||
<?= Helper\form_select('params['.$param_name.']', $columns_list, $values) ?><br/>
|
||||
<?php elseif (Helper\contains($param_name, 'user_id')): ?>
|
||||
<?= Helper\form_label($param_desc, $param_name) ?>
|
||||
<?= Helper\form_select('params['.$param_name.']', $users_list, $values) ?><br/>
|
||||
<?php elseif (Helper\contains($param_name, 'project_id')): ?>
|
||||
<?= Helper\form_label($param_desc, $param_name) ?>
|
||||
<?= Helper\form_select('params['.$param_name.']', $projects_list, $values) ?><br/>
|
||||
<?php elseif (Helper\contains($param_name, 'color_id')): ?>
|
||||
<?= Helper\form_label($param_desc, $param_name) ?>
|
||||
<?= Helper\form_select('params['.$param_name.']', $colors_list, $values) ?><br/>
|
||||
<?php elseif (Helper\contains($param_name, 'category_id')): ?>
|
||||
<?= Helper\form_label($param_desc, $param_name) ?>
|
||||
<?= Helper\form_select('params['.$param_name.']', $categories_list, $values) ?><br/>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save this action') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?> <a href="?controller=action&action=index&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</section>
|
16
sources/app/Templates/action_remove.php
Normal file
16
sources/app/Templates/action_remove.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Remove an automatic action') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info">
|
||||
<?= t('Do you really want to remove this action: "%s"?', Helper\in_list($action['event_name'], $available_events).'/'.Helper\in_list($action['action_name'], $available_actions)) ?>
|
||||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=action&action=remove&action_id=<?= $action['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=action&action=index&project_id=<?= $action['project_id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
9
sources/app/Templates/app_forbidden.php
Normal file
9
sources/app/Templates/app_forbidden.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Forbidden') ?></h2>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-error">
|
||||
<?= t('Access Forbidden') ?>
|
||||
</p>
|
||||
</section>
|
9
sources/app/Templates/app_notfound.php
Normal file
9
sources/app/Templates/app_notfound.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Page not found') ?></h2>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-error">
|
||||
<?= t('Sorry, I didn\'t found this information in my database!') ?>
|
||||
</p>
|
||||
</section>
|
26
sources/app/Templates/board_assign.php
Normal file
26
sources/app/Templates/board_assign.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<section id="main">
|
||||
|
||||
<div class="page-header board">
|
||||
<h2>
|
||||
<?= t('Project "%s"', $current_project_name) ?>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h3><?= t('Change assignee for the task "%s"', $values['title']) ?></h3>
|
||||
<form method="post" action="?controller=board&action=assignTask" autocomplete="off">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Assignee'), 'owner_id') ?>
|
||||
<?= Helper\form_select('owner_id', $users_list, $values, $errors) ?><br/>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?> <a href="?controller=board&action=show&project_id=<?= $values['project_id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
</section>
|
66
sources/app/Templates/board_edit.php
Normal file
66
sources/app/Templates/board_edit.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Edit the board for "%s"', $project['name']) ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=project"><?= t('All projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
|
||||
<h3><?= t('Change columns') ?></h3>
|
||||
<form method="post" action="?controller=board&action=update&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?php $i = 0; ?>
|
||||
<table>
|
||||
<tr>
|
||||
<th><?= t('Position') ?></th>
|
||||
<th><?= t('Column title') ?></th>
|
||||
<th><?= t('Task limit') ?></th>
|
||||
<th><?= t('Actions') ?></th>
|
||||
</tr>
|
||||
<?php foreach ($columns as $column): ?>
|
||||
<tr>
|
||||
<td><?= Helper\form_label(t('Column %d', ++$i), 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?></td>
|
||||
<td><?= Helper\form_text('title['.$column['id'].']', $values, $errors, array('required')) ?></td>
|
||||
<td><?= Helper\form_number('task_limit['.$column['id'].']', $values, $errors, array('placeholder="'.t('limit').'"')) ?></td>
|
||||
<td>
|
||||
<ul>
|
||||
<?php if ($column['position'] != 1): ?>
|
||||
<li>
|
||||
<a href="?controller=board&action=moveUp&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Up') ?></a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if ($column['position'] != count($columns)): ?>
|
||||
<li>
|
||||
<a href="?controller=board&action=moveDown&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Down') ?></a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<li>
|
||||
<a href="?controller=board&action=confirm&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'] ?>"><?= t('Remove') ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Update') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h3><?= t('Add a new column') ?></h3>
|
||||
<form method="post" action="?controller=board&action=add&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
<?= Helper\form_label(t('Title'), 'title') ?>
|
||||
<?= Helper\form_text('title', $values, $errors, array('required')) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Add this column') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</section>
|
31
sources/app/Templates/board_index.php
Normal file
31
sources/app/Templates/board_index.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<section id="main">
|
||||
|
||||
<div class="page-header board">
|
||||
<h2>
|
||||
<?= t('Project "%s"', $current_project_name) ?>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="project-menu">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="hide-tablet"><?= t('Filter by user') ?></span>
|
||||
<?= Helper\form_select('user_id', $users, $filters) ?>
|
||||
</li>
|
||||
<li>
|
||||
<span class="hide-tablet"><?= t('Filter by category') ?></span>
|
||||
<?= Helper\form_select('category_id', $categories, $filters) ?>
|
||||
</li>
|
||||
<li><a href="#" id="filter-due-date"><?= t('Filter by due date') ?></a></li>
|
||||
<li><a href="?controller=project&action=search&project_id=<?= $current_project_id ?>"><?= t('Search') ?></a></li>
|
||||
<li><a href="?controller=project&action=tasks&project_id=<?= $current_project_id ?>"><?= t('Completed tasks') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php if (empty($board)): ?>
|
||||
<p class="alert alert-error"><?= t('There is no column in your project!') ?></p>
|
||||
<?php else: ?>
|
||||
<?= Helper\template('board_show', array('current_project_id' => $current_project_id, 'board' => $board, 'categories' => $categories)) ?>
|
||||
<?php endif ?>
|
||||
|
||||
</section>
|
34
sources/app/Templates/board_public.php
Normal file
34
sources/app/Templates/board_public.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<section id="main" class="public-board">
|
||||
|
||||
<?php if (empty($columns)): ?>
|
||||
<p class="alert alert-error"><?= t('There is no column in your project!') ?></p>
|
||||
<?php else: ?>
|
||||
<table id="board">
|
||||
<tr>
|
||||
<?php $column_with = round(100 / count($columns), 2); ?>
|
||||
<?php foreach ($columns as $column): ?>
|
||||
<th width="<?= $column_with ?>%">
|
||||
<?= Helper\escape($column['title']) ?>
|
||||
<?php if ($column['task_limit']): ?>
|
||||
<span title="<?= t('Task limit') ?>" class="task-limit">(<?= Helper\escape(count($column['tasks']).'/'.$column['task_limit']) ?>)</span>
|
||||
<?php endif ?>
|
||||
</th>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<?php foreach ($columns as $column): ?>
|
||||
<td class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>">
|
||||
<?php foreach ($column['tasks'] as $task): ?>
|
||||
<div class="task-board task-<?= $task['color_id'] ?>">
|
||||
|
||||
<?= Helper\template('board_task', array('task' => $task, 'categories' => $categories, 'not_editable' => true)) ?>
|
||||
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
|
||||
</section>
|
17
sources/app/Templates/board_remove.php
Normal file
17
sources/app/Templates/board_remove.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Remove a column') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info">
|
||||
<?= t('Do you really want to remove this column: "%s"?', $column['title']) ?>
|
||||
<?= t('This action will REMOVE ALL TASKS associated to this column!') ?>
|
||||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=board&action=remove&column_id=<?= $column['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=board&action=edit&project_id=<?= $column['project_id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
49
sources/app/Templates/board_show.php
Normal file
49
sources/app/Templates/board_show.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<table id="board" data-project-id="<?= $current_project_id ?>" data-time="<?= time() ?>" data-check-interval="<?= BOARD_CHECK_INTERVAL ?>" data-csrf-token=<?= \Core\Security::getCSRFToken() ?>>
|
||||
<tr>
|
||||
<?php $column_with = round(100 / count($board), 2); ?>
|
||||
<?php foreach ($board as $column): ?>
|
||||
<th width="<?= $column_with ?>%">
|
||||
<div class="board-add-icon">
|
||||
<a href="?controller=task&action=create&project_id=<?= $column['project_id'] ?>&column_id=<?= $column['id'] ?>" title="<?= t('Add a new task') ?>">+</a>
|
||||
</div>
|
||||
<?= Helper\escape($column['title']) ?>
|
||||
<?php if ($column['task_limit']): ?>
|
||||
<span title="<?= t('Task limit') ?>" class="task-limit">
|
||||
(
|
||||
<span id="task-number-column-<?= $column['id'] ?>"><?= count($column['tasks']) ?></span>
|
||||
/
|
||||
<?= Helper\escape($column['task_limit']) ?>
|
||||
)
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span title="<?= t('Task count') ?>" class="task-count">
|
||||
(<span id="task-number-column-<?= $column['id'] ?>"><?= count($column['tasks']) ?></span>)
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</th>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<?php foreach ($board as $column): ?>
|
||||
<td
|
||||
id="column-<?= $column['id'] ?>"
|
||||
class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>"
|
||||
data-column-id="<?= $column['id'] ?>"
|
||||
data-task-limit="<?= $column['task_limit'] ?>"
|
||||
>
|
||||
<?php foreach ($column['tasks'] as $task): ?>
|
||||
<div class="task-board draggable-item task-<?= $task['color_id'] ?>"
|
||||
data-task-id="<?= $task['id'] ?>"
|
||||
data-owner-id="<?= $task['owner_id'] ?>"
|
||||
data-category-id="<?= $task['category_id'] ?>"
|
||||
data-due-date="<?= $task['date_due'] ?>"
|
||||
title="<?= t('View this task') ?>">
|
||||
|
||||
<?= Helper\template('board_task', array('task' => $task, 'categories' => $categories)) ?>
|
||||
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
<?php endforeach ?>
|
||||
</tr>
|
||||
</table>
|
76
sources/app/Templates/board_task.php
Normal file
76
sources/app/Templates/board_task.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php if (isset($not_editable)): ?>
|
||||
|
||||
#<?= $task['id'] ?> -
|
||||
|
||||
<span class="task-board-user">
|
||||
<?php if (! empty($task['owner_id'])): ?>
|
||||
<?= t('Assigned to %s', $task['username']) ?>
|
||||
<?php else: ?>
|
||||
<span class="task-board-nobody"><?= t('Nobody assigned') ?></span>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<?php if ($task['score']): ?>
|
||||
<span class="task-score"><?= Helper\escape($task['score']) ?></span>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-board-title">
|
||||
<?= Helper\escape($task['title']) ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<a class="task-edit-popover" href="?controller=task&action=edit&task_id=<?= $task['id'] ?>" title="<?= t('Edit this task') ?>">#<?= $task['id'] ?></a> -
|
||||
|
||||
<span class="task-board-user">
|
||||
<?php if (! empty($task['owner_id'])): ?>
|
||||
<a class="assignee-popover" href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>"><?= t('Assigned to %s', $task['username']) ?></a>
|
||||
<?php else: ?>
|
||||
<a class="assignee-popover" href="?controller=board&action=assign&task_id=<?= $task['id'] ?>" title="<?= t('Change assignee') ?>" class="task-board-nobody"><?= t('Nobody assigned') ?></a>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<?php if ($task['score']): ?>
|
||||
<span class="task-score"><?= Helper\escape($task['score']) ?></span>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-board-title">
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>" title="<?= t('View this task') ?>"><?= Helper\escape($task['title']) ?></a>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
|
||||
|
||||
<?php if ($task['category_id']): ?>
|
||||
<div class="task-board-category-container">
|
||||
<span class="task-board-category">
|
||||
<?= Helper\in_list($task['category_id'], $categories) ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
|
||||
<?php if (! empty($task['date_due']) || ! empty($task['nb_files']) || ! empty($task['nb_comments']) || ! empty($task['description'])): ?>
|
||||
<div class="task-board-footer">
|
||||
|
||||
<?php if (! empty($task['date_due'])): ?>
|
||||
<div class="task-board-date">
|
||||
<?= dt('%B %e, %G', $task['date_due']) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="task-board-icons">
|
||||
<?php if (! empty($task['nb_files'])): ?>
|
||||
<?= $task['nb_files'] ?> <i class="fa fa-paperclip" title="<?= t('Attachments') ?>"></i>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($task['nb_comments'])): ?>
|
||||
<?= $task['nb_comments'] ?> <i class="fa fa-comment-o" title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>"></i>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($task['description'])): ?>
|
||||
<a class="task-board-popover" href='?controller=task&action=editDescription&task_id=<?= $task['id'] ?>'><i class="fa fa-file-text-o" title="<?= t('Description') ?>"></i></a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
24
sources/app/Templates/category_edit.php
Normal file
24
sources/app/Templates/category_edit.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Category modification for the project "%s"', $project['name']) ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=project"><?= t('All projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
|
||||
<form method="post" action="?controller=category&action=update&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Category Name'), 'name') ?>
|
||||
<?= Helper\form_text('name', $values, $errors, array('required')) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
</section>
|
49
sources/app/Templates/category_index.php
Normal file
49
sources/app/Templates/category_index.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Categories for the project "%s"', $project['name']) ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=project"><?= t('All projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
|
||||
<?php if (! empty($categories)): ?>
|
||||
<table>
|
||||
<tr>
|
||||
<th><?= t('Category Name') ?></th>
|
||||
<th><?= t('Actions') ?></th>
|
||||
</tr>
|
||||
<?php foreach ($categories as $category_id => $category_name): ?>
|
||||
<tr>
|
||||
<td><?= Helper\escape($category_name) ?></td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="?controller=category&action=edit&project_id=<?= $project['id'] ?>&category_id=<?= $category_id ?>"><?= t('Edit') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=category&action=confirm&project_id=<?= $project['id'] ?>&category_id=<?= $category_id ?>"><?= t('Remove') ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
|
||||
<h3><?= t('Add a new category') ?></h3>
|
||||
<form method="post" action="?controller=category&action=save&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Category Name'), 'name') ?>
|
||||
<?= Helper\form_text('name', $values, $errors, array('required')) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
</section>
|
16
sources/app/Templates/category_remove.php
Normal file
16
sources/app/Templates/category_remove.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Remove a category') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info">
|
||||
<?= t('Do you really want to remove this category: "%s"?', $category['name']) ?>
|
||||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=category&action=remove&project_id=<?= $project['id'] ?>&category_id=<?= $category['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=category&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
17
sources/app/Templates/comment_create.php
Normal file
17
sources/app/Templates/comment_create.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Add a comment') ?></h2>
|
||||
</div>
|
||||
|
||||
<form method="post" action="?controller=comment&action=save&task_id=<?= $task['id'] ?>" autocomplete="off">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('task_id', $values) ?>
|
||||
<?= Helper\form_hidden('user_id', $values) ?>
|
||||
<?= Helper\form_textarea('comment', $values, $errors, array('autofocus', 'required', 'placeholder="'.t('Leave a comment').'"'), 'comment-textarea') ?><br/>
|
||||
<div class="form-help"><a href="http://kanboard.net/documentation/syntax-guide" target="_blank" rel="noreferrer"><?= t('Write your text in Markdown') ?></a></div>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?>
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
16
sources/app/Templates/comment_edit.php
Normal file
16
sources/app/Templates/comment_edit.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Edit a comment') ?></h2>
|
||||
</div>
|
||||
|
||||
<form method="post" action="?controller=comment&action=update&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_textarea('comment', $values, $errors, array('autofocus', 'required', 'placeholder="'.t('Leave a comment').'"'), 'comment-textarea') ?><br/>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Update') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?>
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
9
sources/app/Templates/comment_forbidden.php
Normal file
9
sources/app/Templates/comment_forbidden.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Forbidden') ?></h2>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-error">
|
||||
<?= t('Only administrators or the creator of the comment can access to this page.') ?>
|
||||
</p>
|
||||
</section>
|
16
sources/app/Templates/comment_remove.php
Normal file
16
sources/app/Templates/comment_remove.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Remove a comment') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info">
|
||||
<?= t('Do you really want to remove this comment?') ?>
|
||||
</p>
|
||||
|
||||
<?= Helper\template('comment_show', array('comment' => $comment, 'task' => $task, 'preview' => true)) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=comment&action=remove&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>#comment-<?= $comment['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
28
sources/app/Templates/comment_show.php
Normal file
28
sources/app/Templates/comment_show.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<div class="comment <?= isset($preview) ? 'comment-preview' : '' ?>" id="comment-<?= $comment['id'] ?>">
|
||||
|
||||
<p class="comment-title">
|
||||
<span class="comment-username"><?= Helper\escape($comment['username']) ?></span> @ <span class="comment-date"><?= dt('%B %e, %G at %k:%M %p', $comment['date']) ?></span>
|
||||
</p>
|
||||
|
||||
<div class="comment-inner">
|
||||
|
||||
<?php if (! isset($preview)): ?>
|
||||
<ul class="comment-actions">
|
||||
<li><a href="#comment-<?= $comment['id'] ?>"><?= t('link') ?></a></li>
|
||||
<?php if (Helper\is_admin() || Helper\is_current_user($comment['user_id'])): ?>
|
||||
<li>
|
||||
<a href="?controller=comment&action=confirm&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'] ?>"><?= t('remove') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=comment&action=edit&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'] ?>"><?= t('edit') ?></a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="markdown">
|
||||
<?= Helper\parse($comment['comment']) ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
126
sources/app/Templates/config_index.php
Normal file
126
sources/app/Templates/config_index.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<section id="main">
|
||||
|
||||
<?php if ($user['is_admin']): ?>
|
||||
<div class="page-header">
|
||||
<h2><?= t('Application settings') ?></h2>
|
||||
</div>
|
||||
<section>
|
||||
<form method="post" action="?controller=config&action=save" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<?= Helper\form_label(t('Language'), 'language') ?>
|
||||
<?= Helper\form_select('language', $languages, $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Timezone'), 'timezone') ?>
|
||||
<?= Helper\form_select('timezone', $timezones, $values, $errors) ?><br/>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="page-header">
|
||||
<h2><?= t('User settings') ?></h2>
|
||||
</div>
|
||||
<section class="settings">
|
||||
<ul>
|
||||
<li>
|
||||
<strong><?= t('My default project:') ?> </strong>
|
||||
<?= (isset($user['default_project_id']) && isset($projects[$user['default_project_id']])) ? Helper\escape($projects[$user['default_project_id']]) : t('None') ?>,
|
||||
<a href="?controller=user&action=edit&user_id=<?= $user['id'] ?>"><?= t('edit') ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<?php if ($user['is_admin']): ?>
|
||||
<div class="page-header">
|
||||
<h2><?= t('More information') ?></h2>
|
||||
</div>
|
||||
<section class="settings">
|
||||
<ul>
|
||||
<li><a href="?controller=config&action=tokens<?= Helper\param_csrf() ?>"><?= t('Reset all tokens') ?></a></li>
|
||||
<li>
|
||||
<?= t('Webhooks token:') ?>
|
||||
<strong><?= Helper\escape($values['webhooks_token']) ?></strong>
|
||||
</li>
|
||||
<li>
|
||||
<?= t('API token:') ?>
|
||||
<strong><?= Helper\escape($values['api_token']) ?></strong>
|
||||
</li>
|
||||
<?php if (DB_DRIVER === 'sqlite'): ?>
|
||||
<li>
|
||||
<?= t('Database size:') ?>
|
||||
<strong><?= Helper\format_bytes($db_size) ?></strong>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=config&action=downloadDb<?= Helper\param_csrf() ?>"><?= t('Download the database') ?></a>
|
||||
<?= t('(Gzip compressed Sqlite file)') ?>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=config&action=optimizeDb <?= Helper\param_csrf() ?>"><?= t('Optimize the database') ?></a>
|
||||
<?= t('(VACUUM command)') ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<li>
|
||||
<?= t('Official website:') ?>
|
||||
<a href="http://kanboard.net/" target="_blank" rel="noreferer">http://kanboard.net/</a>
|
||||
</li>
|
||||
<li>
|
||||
<?= t('Application version:') ?>
|
||||
<?= APP_VERSION ?>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="page-header" id="last-logins">
|
||||
<h2><?= t('Last logins') ?></h2>
|
||||
</div>
|
||||
<?php if (! empty($last_logins)): ?>
|
||||
<table class="table-small table-hover">
|
||||
<tr>
|
||||
<th><?= t('Login date') ?></th>
|
||||
<th><?= t('Authentication method') ?></th>
|
||||
<th><?= t('IP address') ?></th>
|
||||
<th><?= t('User agent') ?></th>
|
||||
</tr>
|
||||
<?php foreach($last_logins as $login): ?>
|
||||
<tr>
|
||||
<td><?= dt('%B %e, %G at %k:%M %p', $login['date_creation']) ?></td>
|
||||
<td><?= Helper\escape($login['auth_type']) ?></td>
|
||||
<td><?= Helper\escape($login['ip']) ?></td>
|
||||
<td><?= Helper\escape($login['user_agent']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="page-header" id="remember-me">
|
||||
<h2><?= t('Persistent connections') ?></h2>
|
||||
</div>
|
||||
<?php if (empty($remember_me_sessions)): ?>
|
||||
<p class="alert alert-info"><?= t('No session') ?></p>
|
||||
<?php else: ?>
|
||||
<table class="table-small table-hover">
|
||||
<tr>
|
||||
<th><?= t('Creation date') ?></th>
|
||||
<th><?= t('Expiration date') ?></th>
|
||||
<th><?= t('IP address') ?></th>
|
||||
<th><?= t('User agent') ?></th>
|
||||
<th><?= t('Action') ?></th>
|
||||
</tr>
|
||||
<?php foreach($remember_me_sessions as $session): ?>
|
||||
<tr>
|
||||
<td><?= dt('%B %e, %G at %k:%M %p', $session['date_creation']) ?></td>
|
||||
<td><?= dt('%B %e, %G at %k:%M %p', $session['expiration']) ?></td>
|
||||
<td><?= Helper\escape($session['ip']) ?></td>
|
||||
<td><?= Helper\escape($session['user_agent']) ?></td>
|
||||
<td><a href="?controller=config&action=removeRememberMeToken&id=<?= $session['id'].Helper\param_csrf() ?>"><?= t('Remove') ?></a></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
</section>
|
14
sources/app/Templates/file_new.php
Normal file
14
sources/app/Templates/file_new.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Attach a document') ?></h2>
|
||||
</div>
|
||||
|
||||
<form action="?controller=file&action=save&task_id=<?= $task['id'] ?>" method="post" enctype="multipart/form-data">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<input type="file" name="files[]" multiple />
|
||||
<div class="form-help"><?= t('Maximum size: ') ?><?= is_integer($max_size) ? Helper\format_bytes($max_size) : $max_size ?></div>
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?>
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
6
sources/app/Templates/file_open.php
Normal file
6
sources/app/Templates/file_open.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<div class="page-header">
|
||||
<h2><?= Helper\escape($file['name']) ?></h2>
|
||||
<div class="task-file-viewer">
|
||||
<img src="?controller=file&action=image&file_id=<?= $file['id'] ?>&task_id=<?= $file['task_id'] ?>" alt="<?= Helper\escape($file['name']) ?>"/>
|
||||
</div>
|
||||
</div>
|
14
sources/app/Templates/file_remove.php
Normal file
14
sources/app/Templates/file_remove.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Remove a file') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info">
|
||||
<?= t('Do you really want to remove this file: "%s"?', Helper\escape($file['name'])) ?>
|
||||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=file&action=remove&task_id=<?= $task['id'] ?>&file_id=<?= $file['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
17
sources/app/Templates/file_show.php
Normal file
17
sources/app/Templates/file_show.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Attachments') ?></h2>
|
||||
</div>
|
||||
|
||||
<ul class="task-show-files">
|
||||
<?php foreach ($files as $file): ?>
|
||||
<li>
|
||||
<a href="?controller=file&action=download&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>"><?= Helper\escape($file['name']) ?></a>
|
||||
<span class="task-show-file-actions">
|
||||
<?php if ($file['is_image']): ?>
|
||||
<a href="?controller=file&action=open&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>" class="file-popover"><?= t('open') ?></a>,
|
||||
<?php endif ?>
|
||||
<a href="?controller=file&action=confirm&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>"><?= t('remove') ?></a>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
76
sources/app/Templates/layout.php
Normal file
76
sources/app/Templates/layout.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<?= Helper\js('assets/js/jquery-1.11.1.min.js') ?>
|
||||
<?= Helper\js('assets/js/jquery-ui-1.10.4.custom.min.js') ?>
|
||||
<?= Helper\js('assets/js/jquery.ui.touch-punch.min.js') ?>
|
||||
<?= Helper\js('assets/js/chosen.jquery.min.js') ?>
|
||||
<?= Helper\js('assets/js/app.js') ?>
|
||||
|
||||
<?= Helper\css('assets/css/app.css') ?>
|
||||
<?= Helper\css('assets/css/font-awesome.min.css') ?>
|
||||
<?= Helper\css('assets/css/jquery-ui-1.10.4.custom.css'); ?>
|
||||
<?= Helper\css('assets/css/chosen.min.css'); ?>
|
||||
|
||||
<link rel="icon" type="image/png" href="assets/img/favicon.png">
|
||||
<link rel="apple-touch-icon" href="assets/img/touch-icon-iphone.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="assets/img/touch-icon-ipad.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="assets/img/touch-icon-iphone-retina.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="assets/img/touch-icon-ipad-retina.png">
|
||||
|
||||
<title><?= isset($title) ? Helper\escape($title).' - Kanboard' : 'Kanboard' ?></title>
|
||||
<?php if (isset($auto_refresh)): ?>
|
||||
<meta http-equiv="refresh" content="<?= BOARD_PUBLIC_CHECK_INTERVAL ?>" >
|
||||
<?php endif ?>
|
||||
</head>
|
||||
<body>
|
||||
<?php if (isset($no_layout)): ?>
|
||||
<?= $content_for_layout ?>
|
||||
<?php else: ?>
|
||||
<header>
|
||||
<nav>
|
||||
<a class="logo" href="?">kanboard</a>
|
||||
|
||||
<ul>
|
||||
<?php if (isset($board_selector)): ?>
|
||||
<li>
|
||||
<select id="board-selector" data-placeholder="<?= t('Display another project') ?>">
|
||||
<option value=""></option>
|
||||
<?php foreach($board_selector as $board_id => $board_name): ?>
|
||||
<option value="<?= $board_id ?>"><?= Helper\escape($board_name) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<li <?= isset($menu) && $menu === 'boards' ? 'class="active"' : '' ?>>
|
||||
<a href="?controller=board"><?= t('Boards') ?></a>
|
||||
</li>
|
||||
<li <?= isset($menu) && $menu === 'projects' ? 'class="active"' : '' ?>>
|
||||
<a href="?controller=project"><?= t('Projects') ?></a>
|
||||
</li>
|
||||
<li <?= isset($menu) && $menu === 'users' ? 'class="active"' : '' ?>>
|
||||
<a href="?controller=user"><?= t('Users') ?></a>
|
||||
</li>
|
||||
<li <?= isset($menu) && $menu === 'config' ? 'class="active"' : '' ?>>
|
||||
<a href="?controller=config"><?= t('Settings') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=user&action=logout<?= Helper\param_csrf() ?>"><?= t('Logout') ?></a>
|
||||
(<?= Helper\escape(Helper\get_username()) ?>)
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<section class="page">
|
||||
<?= Helper\flash('<div class="alert alert-success alert-fade-out">%s</div>') ?>
|
||||
<?= Helper\flash_error('<div class="alert alert-error">%s</div>') ?>
|
||||
<?= $content_for_layout ?>
|
||||
</section>
|
||||
<?php endif ?>
|
||||
</body>
|
||||
</html>
|
25
sources/app/Templates/project_edit.php
Normal file
25
sources/app/Templates/project_edit.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Edit project') ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=project"><?= t('All projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
<form method="post" action="?controller=project&action=update&project_id=<?= $values['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Name'), 'name') ?>
|
||||
<?= Helper\form_text('name', $values, $errors, array('required')) ?>
|
||||
|
||||
<?= Helper\form_checkbox('is_active', t('Activated'), 1, isset($values['is_active']) && $values['is_active'] == 1 ? true : false) ?><br/>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</section>
|
100
sources/app/Templates/project_index.php
Normal file
100
sources/app/Templates/project_index.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Projects') ?><span id="page-counter"> (<?= $nb_projects ?>)</span></h2>
|
||||
<?php if (Helper\is_admin()): ?>
|
||||
<ul>
|
||||
<li><a href="?controller=project&action=create"><?= t('New project') ?></a></li>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<section>
|
||||
<?php if (empty($projects)): ?>
|
||||
<p class="alert"><?= t('No project') ?></p>
|
||||
<?php else: ?>
|
||||
<table>
|
||||
<tr>
|
||||
<th><?= t('Project') ?></th>
|
||||
<th><?= t('Status') ?></th>
|
||||
<th><?= t('Tasks') ?></th>
|
||||
<th><?= t('Board') ?></th>
|
||||
|
||||
<?php if (Helper\is_admin()): ?>
|
||||
<th><?= t('Actions') ?></th>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php foreach ($projects as $project): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="?controller=board&action=show&project_id=<?= $project['id'] ?>" title="project_id=<?= $project['id'] ?>"><?= Helper\escape($project['name']) ?></a>
|
||||
</td>
|
||||
<td>
|
||||
<?= $project['is_active'] ? t('Active') : t('Inactive') ?>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<?php if ($project['nb_tasks'] > 0): ?>
|
||||
|
||||
<?php if ($project['nb_active_tasks'] > 0): ?>
|
||||
<li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('%d tasks on the board', $project['nb_active_tasks']) ?></a></li>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($project['nb_inactive_tasks'] > 0): ?>
|
||||
<li><a href="?controller=project&action=tasks&project_id=<?= $project['id'] ?>"><?= t('%d closed tasks', $project['nb_inactive_tasks']) ?></a></li>
|
||||
<?php endif ?>
|
||||
|
||||
<li><?= t('%d tasks in total', $project['nb_tasks']) ?></li>
|
||||
|
||||
<?php else: ?>
|
||||
<li><?= t('no task for this project') ?></li>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<?php foreach ($project['columns'] as $column): ?>
|
||||
<li>
|
||||
<span title="column_id=<?= $column['id'] ?>"><?= Helper\escape($column['title']) ?></span> (<?= $column['nb_active_tasks'] ?>)
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</td>
|
||||
<?php if (Helper\is_admin()): ?>
|
||||
<td>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="?controller=category&action=index&project_id=<?= $project['id'] ?>"><?= t('Categories') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=project&action=edit&project_id=<?= $project['id'] ?>"><?= t('Edit project') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=project&action=users&project_id=<?= $project['id'] ?>"><?= t('Edit users access') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=board&action=edit&project_id=<?= $project['id'] ?>"><?= t('Edit board') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=action&action=index&project_id=<?= $project['id'] ?>"><?= t('Automatic actions') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<?php if ($project['is_active']): ?>
|
||||
<a href="?controller=project&action=disable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Disable') ?></a>
|
||||
<?php else: ?>
|
||||
<a href="?controller=project&action=enable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Enable') ?></a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=project&action=confirm&project_id=<?= $project['id'] ?>"><?= t('Remove') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=board&action=readonly&token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
</section>
|
||||
</section>
|
21
sources/app/Templates/project_new.php
Normal file
21
sources/app/Templates/project_new.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('New project') ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=project"><?= t('All projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
<form method="post" action="?controller=project&action=save" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_label(t('Name'), 'name') ?>
|
||||
<?= Helper\form_text('name', $values, $errors, array('autofocus', 'required')) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</section>
|
16
sources/app/Templates/project_remove.php
Normal file
16
sources/app/Templates/project_remove.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Remove project') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info">
|
||||
<?= t('Do you really want to remove this project: "%s"?', $project['name']) ?>
|
||||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=project&action=remove&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
31
sources/app/Templates/project_search.php
Normal file
31
sources/app/Templates/project_search.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2>
|
||||
<?= t('Search in the project "%s"', $project['name']) ?>
|
||||
<?php if (! empty($nb_tasks)): ?>
|
||||
<span id="page-counter"> (<?= $nb_tasks ?>)</span>
|
||||
<?php endif ?>
|
||||
</h2>
|
||||
<ul>
|
||||
<li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li>
|
||||
<li><a href="?controller=project&action=tasks&project_id=<?= $project['id'] ?>"><?= t('Completed tasks') ?></a></li>
|
||||
<li><a href="?controller=project&action=index"><?= t('List of projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
<form method="get" action="?" autocomplete="off">
|
||||
<?= Helper\form_hidden('controller', $values) ?>
|
||||
<?= Helper\form_hidden('action', $values) ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
<?= Helper\form_text('search', $values, array(), array('autofocus', 'required', 'placeholder="'.t('Search').'"')) ?>
|
||||
<input type="submit" value="<?= t('Search') ?>" class="btn btn-blue"/>
|
||||
</form>
|
||||
|
||||
<?php if (empty($tasks) && ! empty($values['search'])): ?>
|
||||
<p class="alert"><?= t('Nothing found.') ?></p>
|
||||
<?php elseif (! empty($tasks)): ?>
|
||||
<?= Helper\template('task_table', array('tasks' => $tasks, 'categories' => $categories, 'columns' => $columns)) ?>
|
||||
<?php endif ?>
|
||||
|
||||
</section>
|
||||
</section>
|
17
sources/app/Templates/project_tasks.php
Normal file
17
sources/app/Templates/project_tasks.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Completed tasks for "%s"', $project['name']) ?><span id="page-counter"> (<?= $nb_tasks ?>)</span></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li>
|
||||
<li><a href="?controller=project&action=search&project_id=<?= $project['id'] ?>"><?= t('Search') ?></a></li>
|
||||
<li><a href="?controller=project&action=index"><?= t('List of projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
<?php if (empty($tasks)): ?>
|
||||
<p class="alert"><?= t('No task') ?></p>
|
||||
<?php else: ?>
|
||||
<?= Helper\template('task_table', array('tasks' => $tasks, 'categories' => $categories, 'columns' => $columns)) ?>
|
||||
<?php endif ?>
|
||||
</section>
|
||||
</section>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue