Merge pull request #1 from fguillot/master
This commit is contained in:
commit
99f275e5bb
|
|
@ -7,6 +7,7 @@ app/constants.php export-subst
|
|||
.scrutinizer.yml export-ignore
|
||||
.travis.yml export-ignore
|
||||
Dockerfile export-ignore
|
||||
docker-compose.yml export-ignore
|
||||
Makefile export-ignore
|
||||
README.md export-ignore
|
||||
Vagrantfile export-ignore
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ before_script:
|
|||
- phpenv config-rm xdebug.ini
|
||||
- phpenv config-add tests/php.ini
|
||||
- composer install
|
||||
- php -i
|
||||
|
||||
script:
|
||||
- phpunit -c tests/units.$DB.xml
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Contributors:
|
|||
- [Daniel Raknes](https://github.com/danielraknes)
|
||||
- [David-Norris](https://github.com/David-Norris)
|
||||
- [Dmitry](https://github.com/dmkcv)
|
||||
- [Djpadz](https://github.com/djpadz)
|
||||
- [Dj Padzensky](https://github.com/djpadz)
|
||||
- [Draza (bdpsoft)](https://github.com/bdpsoft)
|
||||
- [Eskiso](https://github.com/eSkiSo)
|
||||
- [Esteban Monge](https://github.com/EstebanMonge)
|
||||
|
|
@ -120,6 +120,7 @@ Contributors:
|
|||
- [Vladimir Babin](https://github.com/Chiliec)
|
||||
- [Yannick Ihmels](https://github.com/ihmels)
|
||||
- [Ybarc](https://github.com/ybarc)
|
||||
- [Yu Yongwoo](https://github.com/uyu423)
|
||||
- [Yuichi Murata](https://github.com/yuichi1004)
|
||||
|
||||
There is also many people who have reported bugs or proposed awesome ideas.
|
||||
There is also many people who have reported bugs or proposed awesome ideas.
|
||||
|
|
|
|||
34
ChangeLog
34
ChangeLog
|
|
@ -1,14 +1,41 @@
|
|||
Version 1.0.27 (unreleased)
|
||||
Version 1.0.28 (unreleased)
|
||||
--------------
|
||||
|
||||
New features:
|
||||
|
||||
* Search in activity stream
|
||||
* Search in comments
|
||||
* Search by task creator
|
||||
* Added command line utility to reset user password and to disable 2FA
|
||||
|
||||
Improvements:
|
||||
|
||||
* Filter/Lexer/QueryBuilder refactoring
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Removed PHP notices in comment suppression view
|
||||
|
||||
Version 1.0.27
|
||||
--------------
|
||||
|
||||
New features:
|
||||
|
||||
* Added Markdown editor
|
||||
* Added letter avatar provider
|
||||
* Added pluggable Avatar providers
|
||||
* Added user avatars with pluggable system
|
||||
- Default is a letter based avatar
|
||||
- Gravatar
|
||||
- Avatar Image upload
|
||||
* Added Korean translation
|
||||
|
||||
Improvements:
|
||||
|
||||
* Added more logging for LDAP client
|
||||
* Improve schema migration process
|
||||
* Improve notification configuration form
|
||||
* Handle state in OAuth2 client
|
||||
* Allow to use the original template in overridden templates
|
||||
* Unification of the project header
|
||||
* Refactoring of Javascript code
|
||||
* Improve comments design
|
||||
* Improve task summary sections
|
||||
|
|
@ -27,6 +54,7 @@ Improvements:
|
|||
|
||||
Bug fixes:
|
||||
|
||||
* Fix bad unique constraints in Mysql table user_has_notifications
|
||||
* Force integer type for aggregated metrics (Burndown chart concat values instead of summing)
|
||||
* Fixes cycle time calculation when the start date is defined in the future
|
||||
* Access allowed to any tasks from the shared public board by changing the URL parameters
|
||||
|
|
|
|||
33
Makefile
33
Makefile
|
|
@ -6,7 +6,7 @@ CSS_VENDOR = $(addprefix assets/css/vendor/, $(addsuffix .css, jquery-ui.min jqu
|
|||
|
||||
JS_APP = $(addprefix assets/js/src/, $(addsuffix .js, Namespace App Dropdown Popover Notification Accordion Session Calendar AvgTimeColumnChart BurndownChart CompareHoursColumnChart CumulativeFlowDiagram LeadCycleTimeChart UserRepartitionChart TaskTimeColumnChart TaskRepartitionChart Gantt Column Markdown ProjectPermission ProjectCreation Screenshot FileUpload Search Task Subtask Swimlane BoardColumnView BoardColumnScrolling BoardHorizontalScrolling BoardCollapsedMode BoardDragAndDrop BoardTask BoardPolling Tooltip Bootstrap))
|
||||
JS_VENDOR = $(addprefix assets/js/vendor/, $(addsuffix .js, jquery-1.11.3.min jquery-ui.min jquery-ui-timepicker-addon.min jquery.ui.touch-punch.min chosen.jquery.min moment.min fullcalendar.min mousetrap.min mousetrap-global-bind.min simplemde.min))
|
||||
JS_LANG = $(addprefix assets/js/vendor/lang/, $(addsuffix .js, cs da de es el fi fr hu id it ja nl nb pl pt pt-br ru sv sr th tr zh-cn))
|
||||
JS_LANG = $(addprefix assets/js/vendor/lang/, $(addsuffix .js, cs da de es el fi fr hu id it ja ko nl nb pl pt pt-br ru sv sr th tr zh-cn))
|
||||
|
||||
all: css js
|
||||
|
||||
|
|
@ -56,6 +56,7 @@ archive:
|
|||
@ rm -rf ${BUILD_DIR}/kanboard/Makefile
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/Vagrantfile
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/Dockerfile
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/docker-compose.yml
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/.*.yml
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/*.md
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/*.markdown
|
||||
|
|
@ -71,40 +72,12 @@ archive:
|
|||
@ find ${BUILD_DIR}/kanboard/vendor -name .travis.yml -delete
|
||||
@ find ${BUILD_DIR}/kanboard/vendor -name README.* -delete
|
||||
@ find ${BUILD_DIR}/kanboard/vendor -name .gitignore -delete
|
||||
@ cd ${BUILD_DIR}/kanboard && sed -i.bak s/master/${version}/g app/constants.php && rm -f app/*.bak
|
||||
@ cd ${BUILD_DIR}/kanboard && sed -i.bak 11s/.*/"define('APP_VERSION', '${version}');"/g app/constants.php && rm -f app/*.bak
|
||||
@ cd ${BUILD_DIR} && zip -r kanboard-${version}.zip kanboard > /dev/null
|
||||
@ cd ${BUILD_DIR} && mv kanboard-${version}.zip ${dst}
|
||||
@ cd ${dst} && if [ -L kanboard-latest.zip ]; then unlink kanboard-latest.zip; ln -s kanboard-${version}.zip kanboard-latest.zip; fi
|
||||
@ rm -rf ${BUILD_DIR}/kanboard
|
||||
|
||||
test-archive:
|
||||
@ echo "Build archive with tests: version=${version}, destination=${dst}"
|
||||
@ rm -rf ${BUILD_DIR}/kanboard ${BUILD_DIR}/kanboard-*.zip
|
||||
@ cd ${BUILD_DIR} && git clone --depth 1 -q https://github.com/fguillot/kanboard.git
|
||||
@ cd ${BUILD_DIR}/kanboard && composer --prefer-dist --optimize-autoloader --quiet install
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/data/*
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/.git*
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/.*.yml
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/*.md
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/*.markdown
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/Dockerfile
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/.docker
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/Vagrantfile
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/app.json
|
||||
@ rm -rf ${BUILD_DIR}/kanboard/plugins/.gitignore
|
||||
@ cd ${BUILD_DIR}/kanboard && find ./vendor -name notes -type d -exec rm -rf {} +;
|
||||
@ cd ${BUILD_DIR}/kanboard && find ./vendor -name test -type d -exec rm -rf {} +;
|
||||
@ cd ${BUILD_DIR}/kanboard && find ./vendor -name tests -type d -exec rm -rf {} +;
|
||||
@ find ${BUILD_DIR}/kanboard/vendor -name composer.json -delete
|
||||
@ find ${BUILD_DIR}/kanboard/vendor -name phpunit.xml -delete
|
||||
@ find ${BUILD_DIR}/kanboard/vendor -name .travis.yml -delete
|
||||
@ find ${BUILD_DIR}/kanboard/vendor -name README.* -delete
|
||||
@ find ${BUILD_DIR}/kanboard/vendor -name .gitignore -delete
|
||||
@ cd ${BUILD_DIR}/kanboard && sed -i.bak s/master/${version}/g app/constants.php && rm -f app/*.bak
|
||||
@ cd ${BUILD_DIR} && zip -r kanboard-${version}.zip kanboard > /dev/null
|
||||
@ cd ${BUILD_DIR} && mv kanboard-${version}.zip ${dst}
|
||||
@ rm -rf ${BUILD_DIR}/kanboard
|
||||
|
||||
test-sqlite-coverage:
|
||||
@ phpunit --coverage-html /tmp/coverage --whitelist app/ -c tests/units.sqlite.xml
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Official website: <http://kanboard.net>
|
|||
- [Change Log](https://github.com/fguillot/kanboard/blob/master/ChangeLog)
|
||||
- [Documentation](https://github.com/fguillot/kanboard/blob/master/doc/index.markdown)
|
||||
|
||||
[](https://heroku.com/deploy)
|
||||
[](https://heroku.com/deploy?template=https://github.com/fguillot/kanboard)
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -1,54 +1,16 @@
|
|||
|
||||
$debian_script = <<SCRIPT
|
||||
apt-get update
|
||||
apt-get install -y apache2 php5 php5-gd php5-curl php5-sqlite php5-xdebug php5-ldap
|
||||
apt-get clean -y
|
||||
$script = <<SCRIPT
|
||||
apt-get install -y apache2 php5 php5-sqlite php5-mysql php5-pgsql php5-gd curl unzip && \
|
||||
apt-get clean && \
|
||||
echo "ServerName localhost" >> /etc/apache2/apache2.conf && \
|
||||
sed -ri 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf && \
|
||||
a2enmod rewrite
|
||||
|
||||
sudo sed -ri 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf
|
||||
|
||||
if [ -f /etc/apache2/sites-enabled/000-default ]; then
|
||||
sudo sed -ri 's/AllowOverride None/AllowOverride All/g' /etc/apache2/sites-enabled/000-default
|
||||
fi
|
||||
|
||||
sudo a2enmod rewrite
|
||||
service apache2 restart
|
||||
|
||||
rm -f /var/www/html/index.html
|
||||
date > /etc/vagrant_provisioned_at
|
||||
|
||||
wget -q https://getcomposer.org/composer.phar
|
||||
chmod +x composer.phar
|
||||
sudo mv composer.phar /usr/local/bin/composer
|
||||
|
||||
wget -q https://phar.phpunit.de/phpunit.phar
|
||||
chmod +x phpunit.phar
|
||||
sudo mv phpunit.phar /usr/local/bin/phpunit
|
||||
|
||||
SCRIPT
|
||||
|
||||
$centos_script = <<SCRIPT
|
||||
sudo yum update -y
|
||||
sudo yum install -y httpd php php-cli php-gd php-ldap php-mbstring php-mysql php-pdo php-pgsql php-xml wget
|
||||
|
||||
sudo sed -ri 's/AllowOverride None/AllowOverride All/g' /etc/httpd/conf/httpd.conf
|
||||
|
||||
if [ -x /usr/bin/systemctl ]; then
|
||||
sudo systemctl restart httpd
|
||||
sudo systemctl enable httpd
|
||||
sudo chcon -R -t httpd_sys_content_rw_t /var/www/html/data
|
||||
sudo setsebool -P httpd_can_network_connect=1
|
||||
else
|
||||
sudo service httpd restart
|
||||
sudo chkconfig httpd on
|
||||
fi
|
||||
|
||||
rm -f /var/www/html/index.html
|
||||
date > /etc/vagrant_provisioned_at
|
||||
|
||||
wget -q https://getcomposer.org/composer.phar
|
||||
chmod +x composer.phar
|
||||
sudo mv composer.phar /usr/local/bin/composer
|
||||
|
||||
wget -q https://phar.phpunit.de/phpunit.phar
|
||||
chmod +x phpunit.phar
|
||||
sudo mv phpunit.phar /usr/local/bin/phpunit
|
||||
|
|
@ -59,52 +21,8 @@ Vagrant.configure("2") do |config|
|
|||
|
||||
config.vm.define "ubuntu" do |m|
|
||||
m.vm.box = "ubuntu/trusty64"
|
||||
m.vm.provision "shell", inline: $debian_script
|
||||
m.vm.provision "shell", inline: $script
|
||||
m.vm.synced_folder ".", "/var/www/html", owner: "www-data", group: "www-data"
|
||||
m.vm.network :forwarded_port, guest: 80, host: 8001
|
||||
end
|
||||
|
||||
config.vm.define "debian8" do |m|
|
||||
m.vm.box = "debian/jessie64"
|
||||
m.vm.provision "shell", inline: $debian_script
|
||||
m.vm.synced_folder ".", "/var/www/html", owner: "www-data", group: "www-data"
|
||||
m.vm.network :forwarded_port, guest: 80, host: 8002
|
||||
end
|
||||
|
||||
config.vm.define "debian7" do |m|
|
||||
m.vm.box = "debian/wheezy64"
|
||||
m.vm.provision "shell", inline: $debian_script
|
||||
m.vm.synced_folder ".", "/var/www", owner: "www-data", group: "www-data"
|
||||
m.vm.network :forwarded_port, guest: 80, host: 8003
|
||||
end
|
||||
|
||||
config.vm.define "debian6" do |m|
|
||||
m.vm.box = "bento/debian-6.0.10"
|
||||
m.vm.provision "shell", inline: $debian_script
|
||||
m.vm.synced_folder ".", "/var/www", owner: "www-data", group: "www-data"
|
||||
m.vm.network :forwarded_port, guest: 80, host: 8004
|
||||
end
|
||||
|
||||
config.vm.define "centos7" do |m|
|
||||
m.vm.box = "centos/7"
|
||||
m.vm.provision "shell", inline: $centos_script
|
||||
m.vm.synced_folder ".", "/var/www/html", owner: "apache", group: "apache", type: "rsync",
|
||||
rsync__exclude: ".git/", rsync__auto: true
|
||||
m.vm.network :forwarded_port, guest: 80, host: 8005
|
||||
end
|
||||
|
||||
config.vm.define "centos6" do |m|
|
||||
m.vm.box = "bento/centos-6.7"
|
||||
m.vm.provision "shell", inline: $centos_script
|
||||
m.vm.synced_folder ".", "/var/www/html", owner: "apache", group: "apache", type: "rsync",
|
||||
rsync__exclude: ".git/", rsync__auto: true
|
||||
m.vm.network :forwarded_port, guest: 80, host: 8006
|
||||
end
|
||||
|
||||
config.vm.define "freebsd10" do |m|
|
||||
m.vm.box = "freebsd/FreeBSD-10.2-STABLE"
|
||||
m.vm.base_mac = "080027D14C66"
|
||||
m.ssh.shell = "sh"
|
||||
m.vm.network :forwarded_port, guest: 80, host: 8007
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Me extends Base
|
|||
public function getMyActivityStream()
|
||||
{
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
return $this->projectActivity->getProjects($project_ids, 100);
|
||||
return $this->helper->projectActivity->getProjectsEvents($project_ids, 100);
|
||||
}
|
||||
|
||||
public function createMyPrivateProject($name, $description = null)
|
||||
|
|
|
|||
|
|
@ -53,13 +53,13 @@ class Project extends Base
|
|||
|
||||
public function getProjectActivities(array $project_ids)
|
||||
{
|
||||
return $this->projectActivity->getProjects($project_ids);
|
||||
return $this->helper->projectActivity->getProjectsEvents($project_ids);
|
||||
}
|
||||
|
||||
public function getProjectActivity($project_id)
|
||||
{
|
||||
$this->checkProjectPermission($project_id);
|
||||
return $this->projectActivity->getProject($project_id);
|
||||
return $this->helper->projectActivity->getProjectEvents($project_id);
|
||||
}
|
||||
|
||||
public function createProject($name, $description = null)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ class User extends \Kanboard\Core\Base
|
|||
try {
|
||||
|
||||
$ldap = LdapClient::connect();
|
||||
$ldap->setLogger($this->logger);
|
||||
$user = LdapUser::getUser($ldap, $username);
|
||||
|
||||
if ($user === null) {
|
||||
|
|
|
|||
|
|
@ -63,10 +63,12 @@ class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
|
|||
try {
|
||||
|
||||
$client = LdapClient::connect($this->getLdapUsername(), $this->getLdapPassword());
|
||||
$client->setLogger($this->logger);
|
||||
|
||||
$user = LdapUser::getUser($client, $this->username);
|
||||
|
||||
if ($user === null) {
|
||||
$this->logger->info('User not found in LDAP server');
|
||||
$this->logger->info('User ('.$this->username.') not found in LDAP server');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +76,8 @@ class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
|
|||
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
|
||||
}
|
||||
|
||||
$this->logger->info('Authenticate user: '.$user->getDn());
|
||||
|
||||
if ($client->authenticate($user->getDn(), $this->password)) {
|
||||
$this->userInfo = $user;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use Symfony\Component\Console\Command\Command;
|
|||
* @package console
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator
|
||||
* @property \Kanboard\Export\SubtaskExport $subtaskExport
|
||||
* @property \Kanboard\Export\TaskExport $taskExport
|
||||
* @property \Kanboard\Export\TransitionExport $transitionExport
|
||||
|
|
@ -21,11 +22,12 @@ use Symfony\Component\Console\Command\Command;
|
|||
* @property \Kanboard\Model\ProjectDailyStats $projectDailyStats
|
||||
* @property \Kanboard\Model\Task $task
|
||||
* @property \Kanboard\Model\TaskFinder $taskFinder
|
||||
* @property \Kanboard\Model\User $user
|
||||
* @property \Kanboard\Model\UserNotification $userNotification
|
||||
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
*/
|
||||
abstract class Base extends Command
|
||||
abstract class BaseCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Container instance
|
||||
|
|
@ -7,7 +7,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
|
||||
class Cronjob extends Base
|
||||
class CronjobCommand extends BaseCommand
|
||||
{
|
||||
private $commands = array(
|
||||
'projects:daily-stats',
|
||||
|
|
@ -7,7 +7,7 @@ use RecursiveDirectoryIterator;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LocaleComparator extends Base
|
||||
class LocaleComparatorCommand extends BaseCommand
|
||||
{
|
||||
const REF_LOCALE = 'fr_FR';
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ use DirectoryIterator;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LocaleSync extends Base
|
||||
class LocaleSyncCommand extends BaseCommand
|
||||
{
|
||||
const REF_LOCALE = 'fr_FR';
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ use Symfony\Component\Console\Input\InputArgument;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ProjectDailyColumnStatsExport extends Base
|
||||
class ProjectDailyColumnStatsExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
|
|
@ -6,7 +6,7 @@ use Kanboard\Model\Project;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ProjectDailyStatsCalculation extends Base
|
||||
class ProjectDailyStatsCalculationCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
class ResetPasswordCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('user:reset-password')
|
||||
->setDescription('Change user password')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'Username')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$helper = $this->getHelper('question');
|
||||
$username = $input->getArgument('username');
|
||||
|
||||
$passwordQuestion = new Question('What is the new password for '.$username.'? (characters are not printed)'.PHP_EOL);
|
||||
$passwordQuestion->setHidden(true);
|
||||
$passwordQuestion->setHiddenFallback(false);
|
||||
|
||||
$password = $helper->ask($input, $output, $passwordQuestion);
|
||||
|
||||
$confirmationQuestion = new Question('Confirmation:'.PHP_EOL);
|
||||
$confirmationQuestion->setHidden(true);
|
||||
$confirmationQuestion->setHiddenFallback(false);
|
||||
|
||||
$confirmation = $helper->ask($input, $output, $confirmationQuestion);
|
||||
|
||||
if ($this->validatePassword($output, $password, $confirmation)) {
|
||||
$this->resetPassword($output, $username, $password);
|
||||
}
|
||||
}
|
||||
|
||||
private function validatePassword(OutputInterface $output, $password, $confirmation)
|
||||
{
|
||||
list($valid, $errors) = $this->passwordResetValidator->validateModification(array(
|
||||
'password' => $password,
|
||||
'confirmation' => $confirmation,
|
||||
));
|
||||
|
||||
if (!$valid) {
|
||||
foreach ($errors as $error_list) {
|
||||
foreach ($error_list as $error) {
|
||||
$output->writeln('<error>'.$error.'</error>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
private function resetPassword(OutputInterface $output, $username, $password)
|
||||
{
|
||||
$userId = $this->user->getIdByUsername($username);
|
||||
|
||||
if (empty($userId)) {
|
||||
$output->writeln('<error>User not found</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->user->update(array('id' => $userId, 'password' => $password))) {
|
||||
$output->writeln('<error>Unable to update password</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
$output->writeln('<info>Password updated successfully</info>');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ResetTwoFactorCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('user:reset-2fa')
|
||||
->setDescription('Remove two-factor authentication for a user')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'Username');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$username = $input->getArgument('username');
|
||||
$userId = $this->user->getIdByUsername($username);
|
||||
|
||||
if (empty($userId)) {
|
||||
$output->writeln('<error>User not found</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->user->update(array('id' => $userId, 'twofactor_activated' => 0, 'twofactor_secret' => ''))) {
|
||||
$output->writeln('<error>Unable to update user profile</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
$output->writeln('<info>Two-factor authentication disabled</info>');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ use Symfony\Component\Console\Input\InputArgument;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class SubtaskExport extends Base
|
||||
class SubtaskExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
|
|
@ -7,7 +7,7 @@ use Symfony\Component\Console\Input\InputArgument;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class TaskExport extends Base
|
||||
class TaskExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
|
|
@ -8,7 +8,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class TaskOverdueNotification extends Base
|
||||
class TaskOverdueNotificationCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
|
|
@ -7,7 +7,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
use Kanboard\Model\Task;
|
||||
use Kanboard\Event\TaskListEvent;
|
||||
|
||||
class TaskTrigger extends Base
|
||||
class TaskTriggerCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
|
|
@ -7,7 +7,7 @@ use Symfony\Component\Console\Input\InputArgument;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class TransitionExport extends Base
|
||||
class TransitionExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
|
|
@ -20,7 +20,7 @@ class Activity extends Base
|
|||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->helper->layout->app('activity/project', array(
|
||||
'events' => $this->projectActivity->getProject($project['id']),
|
||||
'events' => $this->helper->projectActivity->getProjectEvents($project['id']),
|
||||
'project' => $project,
|
||||
'title' => t('%s\'s activity', $project['name'])
|
||||
)));
|
||||
|
|
@ -38,7 +38,8 @@ class Activity extends Base
|
|||
$this->response->html($this->helper->layout->task('activity/task', array(
|
||||
'title' => $task['title'],
|
||||
'task' => $task,
|
||||
'events' => $this->projectActivity->getTask($task['id']),
|
||||
'project' => $this->project->getById($task['project_id']),
|
||||
'events' => $this->helper->projectActivity->getTaskEvents($task['id']),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
|
|
@ -44,14 +45,15 @@ class Analytic extends Base
|
|||
public function compareHours()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$params = $this->getProjectFilters('analytic', 'compareHours');
|
||||
$query = $this->taskFilter->create()->filterByProject($params['project']['id'])->getQuery();
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('analytic', 'compareHours', array('project_id' => $project['id']))
|
||||
->setMax(30)
|
||||
->setOrder(TaskModel::TABLE.'.id')
|
||||
->setQuery($query)
|
||||
->setQuery($this->taskQuery
|
||||
->withFilter(new TaskProjectFilter($project['id']))
|
||||
->getQuery()
|
||||
)
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->helper->layout->analytic('analytic/compare_hours', array(
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class App extends Base
|
|||
|
||||
$this->response->html($this->helper->layout->dashboard('app/activity', array(
|
||||
'title' => t('My activity stream'),
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id']), 100),
|
||||
'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermission->getActiveProjectIds($user['id']), 100),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Core\ObjectStorage\ObjectStorageException;
|
||||
use Kanboard\Core\Thumbnail;
|
||||
|
||||
/**
|
||||
* Avatar File Controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AvatarFile extends Base
|
||||
{
|
||||
/**
|
||||
* Display avatar page
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
$this->response->html($this->helper->layout->user('avatar_file/show', array(
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload Avatar
|
||||
*/
|
||||
public function upload()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
if (! $this->avatarFile->uploadFile($user['id'], $this->request->getFileInfo('avatar'))) {
|
||||
$this->flash->failure(t('Unable to upload the file.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('AvatarFile', 'show', array('user_id' => $user['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Avatar image
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$user = $this->getUser();
|
||||
$this->avatarFile->remove($user['id']);
|
||||
$this->response->redirect($this->helper->url->to('AvatarFile', 'show', array('user_id' => $user['id'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Avatar image (public)
|
||||
*/
|
||||
public function image()
|
||||
{
|
||||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
$size = $this->request->getStringParam('size', 48);
|
||||
$filename = $this->avatarFile->getFilename($user_id);
|
||||
$etag = md5($filename.$size);
|
||||
|
||||
$this->response->cache(365 * 86400, $etag);
|
||||
$this->response->contentType('image/jpeg');
|
||||
|
||||
if ($this->request->getHeader('If-None-Match') !== '"'.$etag.'"') {
|
||||
$this->render($filename, $size);
|
||||
} else {
|
||||
$this->response->status(304);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render thumbnail from object storage
|
||||
*
|
||||
* @access private
|
||||
* @param string $filename
|
||||
* @param integer $size
|
||||
*/
|
||||
private function render($filename, $size)
|
||||
{
|
||||
try {
|
||||
$blob = $this->objectStorage->get($filename);
|
||||
|
||||
Thumbnail::createFromString($blob)
|
||||
->resize($size, $size)
|
||||
->toOutput();
|
||||
} catch (ObjectStorageException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -287,60 +287,4 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
|
||||
return $subtask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common method to get project filters
|
||||
*
|
||||
* @access protected
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @return array
|
||||
*/
|
||||
protected function getProjectFilters($controller, $action)
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
|
||||
$board_selector = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
|
||||
unset($board_selector[$project['id']]);
|
||||
|
||||
$filters = array(
|
||||
'controller' => $controller,
|
||||
'action' => $action,
|
||||
'project_id' => $project['id'],
|
||||
'search' => urldecode($search),
|
||||
);
|
||||
|
||||
$this->userSession->setFilters($project['id'], $filters['search']);
|
||||
|
||||
return array(
|
||||
'project' => $project,
|
||||
'board_selector' => $board_selector,
|
||||
'filters' => $filters,
|
||||
'title' => $project['name'],
|
||||
'description' => $this->getProjectDescription($project),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project description
|
||||
*
|
||||
* @access protected
|
||||
* @param array &$project
|
||||
* @return string
|
||||
*/
|
||||
protected function getProjectDescription(array &$project)
|
||||
{
|
||||
if ($project['owner_id'] > 0) {
|
||||
$description = t('Project owner: ').'**'.$this->helper->text->e($project['owner_name'] ?: $project['owner_username']).'**'.PHP_EOL.PHP_EOL;
|
||||
|
||||
if (! empty($project['description'])) {
|
||||
$description .= '***'.PHP_EOL.PHP_EOL;
|
||||
$description .= $project['description'];
|
||||
}
|
||||
} else {
|
||||
$description = $project['description'];
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Formatter\BoardFormatter;
|
||||
|
||||
/**
|
||||
* Board controller
|
||||
*
|
||||
|
|
@ -47,16 +49,19 @@ class Board extends Base
|
|||
*/
|
||||
public function show()
|
||||
{
|
||||
$params = $this->getProjectFilters('board', 'show');
|
||||
$project = $this->getProject();
|
||||
$search = $this->helper->projectHeader->getSearchQuery($project);
|
||||
|
||||
$this->response->html($this->helper->layout->app('board/view_private', array(
|
||||
'categories_list' => $this->category->getList($params['project']['id'], false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
|
||||
'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()),
|
||||
'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['project']['id']),
|
||||
'project' => $project,
|
||||
'title' => $project['name'],
|
||||
'description' => $this->helper->projectHeader->getDescription($project),
|
||||
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
|
||||
'board_highlight_period' => $this->config->get('board_highlight_period'),
|
||||
) + $params));
|
||||
'swimlanes' => $this->taskLexer
|
||||
->build($search)
|
||||
->format(BoardFormatter::getInstance($this->container)->setProjectId($project['id']))
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -177,9 +182,11 @@ class Board extends Base
|
|||
{
|
||||
return $this->template->render('board/table_container', array(
|
||||
'project' => $this->project->getById($project_id),
|
||||
'swimlanes' => $this->taskFilter->search($this->userSession->getFilters($project_id))->getBoard($project_id),
|
||||
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
|
||||
'board_highlight_period' => $this->config->get('board_highlight_period'),
|
||||
'swimlanes' => $this->taskLexer
|
||||
->build($this->userSession->getFilters($project_id))
|
||||
->format(BoardFormatter::getInstance($this->container)->setProjectId($project_id))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Filter\TaskStatusFilter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
|
|
@ -20,9 +23,14 @@ class Calendar extends Base
|
|||
*/
|
||||
public function show()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
|
||||
$this->response->html($this->helper->layout->app('calendar/show', array(
|
||||
'project' => $project,
|
||||
'title' => $project['name'],
|
||||
'description' => $this->helper->projectHeader->getDescription($project),
|
||||
'check_interval' => $this->config->get('board_private_refresh_interval'),
|
||||
) + $this->getProjectFilters('calendar', 'show')));
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -35,21 +43,11 @@ class Calendar extends Base
|
|||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$start = $this->request->getStringParam('start');
|
||||
$end = $this->request->getStringParam('end');
|
||||
$search = $this->userSession->getFilters($project_id);
|
||||
$queryBuilder = $this->taskLexer->build($search)->withFilter(new TaskProjectFilter($project_id));
|
||||
|
||||
// Common filter
|
||||
$filter = $this->taskFilterCalendarFormatter
|
||||
->search($this->userSession->getFilters($project_id))
|
||||
->filterByProject($project_id);
|
||||
|
||||
// Tasks
|
||||
if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') {
|
||||
$events = $filter->copy()->filterByCreationDateRange($start, $end)->setColumns('date_creation', 'date_completed')->format();
|
||||
} else {
|
||||
$events = $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format();
|
||||
}
|
||||
|
||||
// Tasks with due date
|
||||
$events = array_merge($events, $filter->copy()->filterByDueDateRange($start, $end)->setColumns('date_due')->setFullDay()->format());
|
||||
$events = $this->helper->calendar->getTaskDateDueEvents(clone($queryBuilder), $start, $end);
|
||||
$events = array_merge($events, $this->helper->calendar->getTaskEvents(clone($queryBuilder), $start, $end));
|
||||
|
||||
$events = $this->hook->merge('controller:calendar:project:events', $events, array(
|
||||
'project_id' => $project_id,
|
||||
|
|
@ -70,21 +68,15 @@ class Calendar extends Base
|
|||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
$start = $this->request->getStringParam('start');
|
||||
$end = $this->request->getStringParam('end');
|
||||
$filter = $this->taskFilterCalendarFormatter->create()->filterByOwner($user_id)->filterByStatus(TaskModel::STATUS_OPEN);
|
||||
$queryBuilder = $this->taskQuery
|
||||
->withFilter(new TaskAssigneeFilter($user_id))
|
||||
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN));
|
||||
|
||||
// Task with due date
|
||||
$events = $filter->copy()->filterByDueDateRange($start, $end)->setColumns('date_due')->setFullDay()->format();
|
||||
$events = $this->helper->calendar->getTaskDateDueEvents(clone($queryBuilder), $start, $end);
|
||||
$events = array_merge($events, $this->helper->calendar->getTaskEvents(clone($queryBuilder), $start, $end));
|
||||
|
||||
// Tasks
|
||||
if ($this->config->get('calendar_user_tasks', 'date_started') === 'date_creation') {
|
||||
$events = array_merge($events, $filter->copy()->filterByCreationDateRange($start, $end)->setColumns('date_creation', 'date_completed')->format());
|
||||
} else {
|
||||
$events = array_merge($events, $filter->copy()->filterByStartDateRange($start, $end)->setColumns('date_started', 'date_completed')->format());
|
||||
}
|
||||
|
||||
// Subtasks time tracking
|
||||
if ($this->config->get('calendar_user_subtasks_time_tracking') == 1) {
|
||||
$events = array_merge($events, $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end));
|
||||
$events = array_merge($events, $this->helper->calendar->getSubtaskTimeTrackingEvents($user_id, $start, $end));
|
||||
}
|
||||
|
||||
$events = $this->hook->merge('controller:calendar:user:events', $events, array(
|
||||
|
|
|
|||
|
|
@ -5,54 +5,32 @@ namespace Kanboard\Controller;
|
|||
use Parsedown;
|
||||
|
||||
/**
|
||||
* Documentation controller
|
||||
* Documentation Viewer
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Doc extends Base
|
||||
{
|
||||
private function readFile($filename)
|
||||
{
|
||||
$url = $this->helper->url;
|
||||
$data = file_get_contents($filename);
|
||||
list($title, ) = explode("\n", $data, 2);
|
||||
|
||||
$replaceUrl = function (array $matches) use ($url) {
|
||||
return '('.$url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')';
|
||||
};
|
||||
|
||||
$content = preg_replace_callback('/\((.*.markdown)\)/', $replaceUrl, $data);
|
||||
|
||||
return array(
|
||||
'content' => Parsedown::instance()->text($content),
|
||||
'title' => $title !== 'Documentation' ? t('Documentation: %s', $title) : $title,
|
||||
);
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
$page = $this->request->getStringParam('file', 'index');
|
||||
|
||||
if (! preg_match('/^[a-z0-9\-]+/', $page)) {
|
||||
if (!preg_match('/^[a-z0-9\-]+/', $page)) {
|
||||
$page = 'index';
|
||||
}
|
||||
|
||||
$filenames = array(__DIR__.'/../../doc/'.$page.'.markdown');
|
||||
$filename = __DIR__.'/../../doc/index.markdown';
|
||||
|
||||
if ($this->config->getCurrentLanguage() === 'fr_FR') {
|
||||
array_unshift($filenames, __DIR__.'/../../doc/fr/'.$page.'.markdown');
|
||||
$filename = __DIR__.'/../../doc/fr/' . $page . '.markdown';
|
||||
} else {
|
||||
$filename = __DIR__ . '/../../doc/' . $page . '.markdown';
|
||||
}
|
||||
|
||||
foreach ($filenames as $file) {
|
||||
if (file_exists($file)) {
|
||||
$filename = $file;
|
||||
break;
|
||||
}
|
||||
if (!file_exists($filename)) {
|
||||
$filename = __DIR__.'/../../doc/index.markdown';
|
||||
}
|
||||
|
||||
$this->response->html($this->helper->layout->app('doc/show', $this->readFile($filename)));
|
||||
$this->response->html($this->helper->layout->app('doc/show', $this->render($filename)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -62,4 +40,53 @@ class Doc extends Base
|
|||
{
|
||||
$this->response->html($this->template->render('config/keyboard_shortcuts'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare Markdown file
|
||||
*
|
||||
* @access private
|
||||
* @param string $filename
|
||||
* @return array
|
||||
*/
|
||||
private function render($filename)
|
||||
{
|
||||
$data = file_get_contents($filename);
|
||||
$content = preg_replace_callback('/\((.*.markdown)\)/', array($this, 'replaceMarkdownUrl'), $data);
|
||||
$content = preg_replace_callback('/\((screenshots.*\.png)\)/', array($this, 'replaceImageUrl'), $content);
|
||||
|
||||
list($title, ) = explode("\n", $data, 2);
|
||||
|
||||
return array(
|
||||
'content' => Parsedown::instance()->text($content),
|
||||
'title' => $title !== 'Documentation' ? t('Documentation: %s', $title) : $title,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex callback to replace Markdown links
|
||||
*
|
||||
* @access public
|
||||
* @param array $matches
|
||||
* @return string
|
||||
*/
|
||||
public function replaceMarkdownUrl(array $matches)
|
||||
{
|
||||
return '('.$this->helper->url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex callback to replace image links
|
||||
*
|
||||
* @access public
|
||||
* @param array $matches
|
||||
* @return string
|
||||
*/
|
||||
public function replaceImageUrl(array $matches)
|
||||
{
|
||||
if ($this->config->getCurrentLanguage() === 'fr_FR') {
|
||||
return '('.$this->helper->url->base().'doc/fr/'.$matches[1].')';
|
||||
}
|
||||
|
||||
return '('.$this->helper->url->base().'doc/'.$matches[1].')';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class Feed extends Base
|
|||
}
|
||||
|
||||
$this->response->xml($this->template->render('feed/user', array(
|
||||
'events' => $this->projectActivity->getProjects($this->projectPermission->getActiveProjectIds($user['id'])),
|
||||
'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermission->getActiveProjectIds($user['id'])),
|
||||
'user' => $user,
|
||||
)));
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ class Feed extends Base
|
|||
}
|
||||
|
||||
$this->response->xml($this->template->render('feed/project', array(
|
||||
'events' => $this->projectActivity->getProject($project['id']),
|
||||
'events' => $this->helper->projectActivity->getProjectEvents($project['id']),
|
||||
'project' => $project,
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,9 +66,16 @@ class FileViewer extends Base
|
|||
*/
|
||||
public function image()
|
||||
{
|
||||
$file = $this->getFile();
|
||||
$etag = md5($file['path']);
|
||||
$this->response->contentType($this->helper->file->getImageMimeType($file['name']));
|
||||
$this->response->cache(5 * 86400, $etag);
|
||||
|
||||
if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
|
||||
return $this->response->status(304);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $this->getFile();
|
||||
$this->response->contentType($this->helper->file->getImageMimeType($file['name']));
|
||||
$this->objectStorage->output($file['path']);
|
||||
} catch (ObjectStorageException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
|
|
@ -82,12 +89,21 @@ class FileViewer extends Base
|
|||
*/
|
||||
public function thumbnail()
|
||||
{
|
||||
$file = $this->getFile();
|
||||
$model = $file['model'];
|
||||
$filename = $this->$model->getThumbnailPath($file['path']);
|
||||
$etag = md5($filename);
|
||||
|
||||
$this->response->cache(5 * 86400, $etag);
|
||||
$this->response->contentType('image/jpeg');
|
||||
|
||||
if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
|
||||
return $this->response->status(304);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $this->getFile();
|
||||
$model = $file['model'];
|
||||
$this->objectStorage->output($this->$model->getThumbnailPath($file['path']));
|
||||
|
||||
$this->objectStorage->output($filename);
|
||||
} catch (ObjectStorageException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\ProjectIdsFilter;
|
||||
use Kanboard\Filter\ProjectStatusFilter;
|
||||
use Kanboard\Filter\ProjectTypeFilter;
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Formatter\ProjectGanttFormatter;
|
||||
use Kanboard\Formatter\TaskGanttFormatter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
use Kanboard\Model\Project as ProjectModel;
|
||||
|
||||
/**
|
||||
* Gantt controller
|
||||
|
|
@ -17,14 +24,16 @@ class Gantt extends Base
|
|||
*/
|
||||
public function projects()
|
||||
{
|
||||
if ($this->userSession->isAdmin()) {
|
||||
$project_ids = $this->project->getAllIds();
|
||||
} else {
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
}
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$filter = $this->projectQuery
|
||||
->withFilter(new ProjectTypeFilter(ProjectModel::TYPE_TEAM))
|
||||
->withFilter(new ProjectStatusFilter(ProjectModel::ACTIVE))
|
||||
->withFilter(new ProjectIdsFilter($project_ids));
|
||||
|
||||
$filter->getQuery()->asc(ProjectModel::TABLE.'.start_date');
|
||||
|
||||
$this->response->html($this->helper->layout->app('gantt/projects', array(
|
||||
'projects' => $this->projectGanttFormatter->filter($project_ids)->format(),
|
||||
'projects' => $filter->format(new ProjectGanttFormatter($this->container)),
|
||||
'title' => t('Gantt chart for all projects'),
|
||||
)));
|
||||
}
|
||||
|
|
@ -54,9 +63,10 @@ class Gantt extends Base
|
|||
*/
|
||||
public function project()
|
||||
{
|
||||
$params = $this->getProjectFilters('gantt', 'project');
|
||||
$filter = $this->taskFilterGanttFormatter->search($params['filters']['search'])->filterByProject($params['project']['id']);
|
||||
$project = $this->getProject();
|
||||
$search = $this->helper->projectHeader->getSearchQuery($project);
|
||||
$sorting = $this->request->getStringParam('sorting', 'board');
|
||||
$filter = $this->taskLexer->build($search)->withFilter(new TaskProjectFilter($project['id']));
|
||||
|
||||
if ($sorting === 'date') {
|
||||
$filter->getQuery()->asc(TaskModel::TABLE.'.date_started')->asc(TaskModel::TABLE.'.date_creation');
|
||||
|
|
@ -64,10 +74,12 @@ class Gantt extends Base
|
|||
$filter->getQuery()->asc('column_position')->asc(TaskModel::TABLE.'.position');
|
||||
}
|
||||
|
||||
$this->response->html($this->helper->layout->app('gantt/project', $params + array(
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
|
||||
$this->response->html($this->helper->layout->app('gantt/project', array(
|
||||
'project' => $project,
|
||||
'title' => $project['name'],
|
||||
'description' => $this->helper->projectHeader->getDescription($project),
|
||||
'sorting' => $sorting,
|
||||
'tasks' => $filter->format(),
|
||||
'tasks' => $filter->format(new TaskGanttFormatter($this->container)),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Formatter\GroupAutoCompleteFormatter;
|
||||
|
||||
/**
|
||||
* Group Helper
|
||||
*
|
||||
|
|
@ -11,14 +13,14 @@ namespace Kanboard\Controller;
|
|||
class GroupHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Group autocompletion (Ajax)
|
||||
* Group auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$groups = $this->groupManager->find($search);
|
||||
$this->response->json($this->groupAutoCompleteFormatter->setGroups($groups)->format());
|
||||
$formatter = new GroupAutoCompleteFormatter($this->groupManager->find($search));
|
||||
$this->response->json($formatter->format());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Model\TaskFilter;
|
||||
use Kanboard\Core\Filter\QueryBuilder;
|
||||
use Kanboard\Filter\TaskAssigneeFilter;
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Filter\TaskStatusFilter;
|
||||
use Kanboard\Formatter\TaskICalFormatter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
use Eluceo\iCal\Component\Calendar as iCalendar;
|
||||
|
||||
|
|
@ -30,10 +34,11 @@ class Ical extends Base
|
|||
}
|
||||
|
||||
// Common filter
|
||||
$filter = $this->taskFilterICalendarFormatter
|
||||
->create()
|
||||
->filterByStatus(TaskModel::STATUS_OPEN)
|
||||
->filterByOwner($user['id']);
|
||||
$queryBuilder = new QueryBuilder();
|
||||
$queryBuilder
|
||||
->withQuery($this->taskFinder->getICalQuery())
|
||||
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
|
||||
->withFilter(new TaskAssigneeFilter($user['id']));
|
||||
|
||||
// Calendar properties
|
||||
$calendar = new iCalendar('Kanboard');
|
||||
|
|
@ -41,7 +46,7 @@ class Ical extends Base
|
|||
$calendar->setDescription($user['name'] ?: $user['username']);
|
||||
$calendar->setPublishedTTL('PT1H');
|
||||
|
||||
$this->renderCalendar($filter, $calendar);
|
||||
$this->renderCalendar($queryBuilder, $calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,10 +65,11 @@ class Ical extends Base
|
|||
}
|
||||
|
||||
// Common filter
|
||||
$filter = $this->taskFilterICalendarFormatter
|
||||
->create()
|
||||
->filterByStatus(TaskModel::STATUS_OPEN)
|
||||
->filterByProject($project['id']);
|
||||
$queryBuilder = new QueryBuilder();
|
||||
$queryBuilder
|
||||
->withQuery($this->taskFinder->getICalQuery())
|
||||
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
|
||||
->withFilter(new TaskProjectFilter($project['id']));
|
||||
|
||||
// Calendar properties
|
||||
$calendar = new iCalendar('Kanboard');
|
||||
|
|
@ -71,7 +77,7 @@ class Ical extends Base
|
|||
$calendar->setDescription($project['name']);
|
||||
$calendar->setPublishedTTL('PT1H');
|
||||
|
||||
$this->renderCalendar($filter, $calendar);
|
||||
$this->renderCalendar($queryBuilder, $calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -79,37 +85,14 @@ class Ical extends Base
|
|||
*
|
||||
* @access private
|
||||
*/
|
||||
private function renderCalendar(TaskFilter $filter, iCalendar $calendar)
|
||||
private function renderCalendar(QueryBuilder $queryBuilder, iCalendar $calendar)
|
||||
{
|
||||
$start = $this->request->getStringParam('start', strtotime('-2 month'));
|
||||
$end = $this->request->getStringParam('end', strtotime('+6 months'));
|
||||
|
||||
// Tasks
|
||||
if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') {
|
||||
$filter
|
||||
->copy()
|
||||
->filterByCreationDateRange($start, $end)
|
||||
->setColumns('date_creation', 'date_completed')
|
||||
->setCalendar($calendar)
|
||||
->addDateTimeEvents();
|
||||
} else {
|
||||
$filter
|
||||
->copy()
|
||||
->filterByStartDateRange($start, $end)
|
||||
->setColumns('date_started', 'date_completed')
|
||||
->setCalendar($calendar)
|
||||
->addDateTimeEvents($calendar);
|
||||
}
|
||||
$this->helper->ical->addTaskDateDueEvents($queryBuilder, $calendar, $start, $end);
|
||||
|
||||
// Tasks with due date
|
||||
$filter
|
||||
->copy()
|
||||
->filterByDueDateRange($start, $end)
|
||||
->setColumns('date_due')
|
||||
->setCalendar($calendar)
|
||||
->addFullDayEvents($calendar);
|
||||
|
||||
$this->response->contentType('text/calendar; charset=utf-8');
|
||||
echo $filter->setCalendar($calendar)->format();
|
||||
$formatter = new TaskICalFormatter($this->container);
|
||||
$this->response->ical($formatter->setCalendar($calendar)->format());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
|
|
@ -19,22 +20,26 @@ class Listing extends Base
|
|||
*/
|
||||
public function show()
|
||||
{
|
||||
$params = $this->getProjectFilters('listing', 'show');
|
||||
$query = $this->taskFilter->search($params['filters']['search'])->filterByProject($params['project']['id'])->getQuery();
|
||||
$project = $this->getProject();
|
||||
$search = $this->helper->projectHeader->getSearchQuery($project);
|
||||
|
||||
$paginator = $this->paginator
|
||||
->setUrl('listing', 'show', array('project_id' => $params['project']['id']))
|
||||
->setUrl('listing', 'show', array('project_id' => $project['id']))
|
||||
->setMax(30)
|
||||
->setOrder(TaskModel::TABLE.'.id')
|
||||
->setDirection('DESC')
|
||||
->setQuery($query)
|
||||
->setQuery($this->taskLexer
|
||||
->build($search)
|
||||
->withFilter(new TaskProjectFilter($project['id']))
|
||||
->getQuery()
|
||||
)
|
||||
->calculate();
|
||||
|
||||
$this->response->html($this->helper->layout->app('listing/show', $params + array(
|
||||
$this->response->html($this->helper->layout->app('listing/show', array(
|
||||
'project' => $project,
|
||||
'title' => $project['name'],
|
||||
'description' => $this->helper->projectHeader->getDescription($project),
|
||||
'paginator' => $paginator,
|
||||
'categories_list' => $this->category->getList($params['project']['id'], false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
|
||||
'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
|
||||
|
||||
/**
|
||||
* OAuth controller
|
||||
*
|
||||
|
|
@ -10,6 +12,72 @@ namespace Kanboard\Controller;
|
|||
*/
|
||||
class Oauth extends Base
|
||||
{
|
||||
/**
|
||||
* Redirect to the provider if no code received
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
protected function step1($provider)
|
||||
{
|
||||
$code = $this->request->getStringParam('code');
|
||||
$state = $this->request->getStringParam('state');
|
||||
|
||||
if (! empty($code)) {
|
||||
$this->step2($provider, $code, $state);
|
||||
} else {
|
||||
$this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link or authenticate the user
|
||||
*
|
||||
* @access protected
|
||||
* @param string $providerName
|
||||
* @param string $code
|
||||
* @param string $state
|
||||
*/
|
||||
protected function step2($providerName, $code, $state)
|
||||
{
|
||||
$provider = $this->authenticationManager->getProvider($providerName);
|
||||
$provider->setCode($code);
|
||||
$hasValidState = $provider->getService()->isValidateState($state);
|
||||
|
||||
if ($this->userSession->isLogged()) {
|
||||
if ($hasValidState) {
|
||||
$this->link($provider);
|
||||
} else {
|
||||
$this->flash->failure(t('The OAuth2 state parameter is invalid'));
|
||||
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
|
||||
}
|
||||
} else {
|
||||
if ($hasValidState) {
|
||||
$this->authenticate($providerName);
|
||||
} else {
|
||||
$this->authenticationFailure(t('The OAuth2 state parameter is invalid'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link the account
|
||||
*
|
||||
* @access protected
|
||||
* @param OAuthAuthenticationProviderInterface $provider
|
||||
*/
|
||||
protected function link(OAuthAuthenticationProviderInterface $provider)
|
||||
{
|
||||
if (! $provider->authenticate()) {
|
||||
$this->flash->failure(t('External authentication failed'));
|
||||
} else {
|
||||
$this->userProfile->assign($this->userSession->getId(), $provider->getUser());
|
||||
$this->flash->success(t('Your external account is linked to your profile successfully.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink external account
|
||||
*
|
||||
|
|
@ -29,78 +97,34 @@ class Oauth extends Base
|
|||
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the provider if no code received
|
||||
*
|
||||
* @access private
|
||||
* @param string $provider
|
||||
*/
|
||||
protected function step1($provider)
|
||||
{
|
||||
$code = $this->request->getStringParam('code');
|
||||
|
||||
if (! empty($code)) {
|
||||
$this->step2($provider, $code);
|
||||
} else {
|
||||
$this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link or authenticate the user
|
||||
*
|
||||
* @access protected
|
||||
* @param string $provider
|
||||
* @param string $code
|
||||
*/
|
||||
protected function step2($provider, $code)
|
||||
{
|
||||
$this->authenticationManager->getProvider($provider)->setCode($code);
|
||||
|
||||
if ($this->userSession->isLogged()) {
|
||||
$this->link($provider);
|
||||
}
|
||||
|
||||
$this->authenticate($provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link the account
|
||||
*
|
||||
* @access protected
|
||||
* @param string $provider
|
||||
*/
|
||||
protected function link($provider)
|
||||
{
|
||||
$authProvider = $this->authenticationManager->getProvider($provider);
|
||||
|
||||
if (! $authProvider->authenticate()) {
|
||||
$this->flash->failure(t('External authentication failed'));
|
||||
} else {
|
||||
$this->userProfile->assign($this->userSession->getId(), $authProvider->getUser());
|
||||
$this->flash->success(t('Your external account is linked to your profile successfully.'));
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the account
|
||||
*
|
||||
* @access protected
|
||||
* @param string $provider
|
||||
* @param string $providerName
|
||||
*/
|
||||
protected function authenticate($provider)
|
||||
protected function authenticate($providerName)
|
||||
{
|
||||
if ($this->authenticationManager->oauthAuthentication($provider)) {
|
||||
if ($this->authenticationManager->oauthAuthentication($providerName)) {
|
||||
$this->response->redirect($this->helper->url->to('app', 'index'));
|
||||
} else {
|
||||
$this->response->html($this->helper->layout->app('auth/index', array(
|
||||
'errors' => array('login' => t('External authentication failed')),
|
||||
'values' => array(),
|
||||
'no_layout' => true,
|
||||
'title' => t('Login')
|
||||
)));
|
||||
$this->authenticationFailure(t('External authentication failed'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show login failure page
|
||||
*
|
||||
* @access protected
|
||||
* @param string $message
|
||||
*/
|
||||
protected function authenticationFailure($message)
|
||||
{
|
||||
$this->response->html($this->helper->layout->app('auth/index', array(
|
||||
'errors' => array('login' => $message),
|
||||
'values' => array(),
|
||||
'no_layout' => true,
|
||||
'title' => t('Login')
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,18 @@ class ProjectOverview extends Base
|
|||
*/
|
||||
public function show()
|
||||
{
|
||||
$params = $this->getProjectFilters('ProjectOverview', 'show');
|
||||
$params['users'] = $this->projectUserRole->getAllUsersGroupedByRole($params['project']['id']);
|
||||
$params['roles'] = $this->role->getProjectRoles();
|
||||
$params['events'] = $this->projectActivity->getProject($params['project']['id'], 10);
|
||||
$params['images'] = $this->projectFile->getAllImages($params['project']['id']);
|
||||
$params['files'] = $this->projectFile->getAllDocuments($params['project']['id']);
|
||||
$project = $this->getProject();
|
||||
$this->project->getColumnStats($project);
|
||||
|
||||
$this->project->getColumnStats($params['project']);
|
||||
|
||||
$this->response->html($this->helper->layout->app('project_overview/show', $params));
|
||||
$this->response->html($this->helper->layout->app('project_overview/show', array(
|
||||
'project' => $project,
|
||||
'title' => $project['name'],
|
||||
'description' => $this->helper->projectHeader->getDescription($project),
|
||||
'users' => $this->projectUserRole->getAllUsersGroupedByRole($project['id']),
|
||||
'roles' => $this->role->getProjectRoles(),
|
||||
'events' => $this->helper->projectActivity->getProjectEvents($project['id'], 10),
|
||||
'images' => $this->projectFile->getAllImages($project['id']),
|
||||
'files' => $this->projectFile->getAllDocuments($project['id']),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,9 @@ class ProjectPermission extends Base
|
|||
$project = $this->getProject();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) {
|
||||
if (empty($values['user_id'])) {
|
||||
$this->flash->failure(t('User not found.'));
|
||||
} elseif ($this->projectUserRole->addUser($values['project_id'], $values['user_id'], $values['role'])) {
|
||||
$this->flash->success(t('Project updated successfully.'));
|
||||
} else {
|
||||
$this->flash->failure(t('Unable to update this project.'));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskProjectsFilter;
|
||||
|
||||
/**
|
||||
* Search controller
|
||||
*
|
||||
|
|
@ -23,14 +25,12 @@ class Search extends Base
|
|||
->setDirection('DESC');
|
||||
|
||||
if ($search !== '' && ! empty($projects)) {
|
||||
$query = $this
|
||||
->taskFilter
|
||||
->search($search)
|
||||
->filterByProjects(array_keys($projects))
|
||||
->getQuery();
|
||||
|
||||
$paginator
|
||||
->setQuery($query)
|
||||
->setQuery($this->taskLexer
|
||||
->build($search)
|
||||
->withFilter(new TaskProjectsFilter(array_keys($projects)))
|
||||
->getQuery()
|
||||
)
|
||||
->calculate();
|
||||
|
||||
$nb_tasks = $paginator->getTotal();
|
||||
|
|
@ -46,4 +46,22 @@ class Search extends Base
|
|||
'title' => t('Search tasks').($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '')
|
||||
)));
|
||||
}
|
||||
|
||||
public function activity()
|
||||
{
|
||||
$search = urldecode($this->request->getStringParam('search'));
|
||||
$events = $this->helper->projectActivity->searchEvents($search);
|
||||
$nb_events = count($events);
|
||||
|
||||
$this->response->html($this->helper->layout->app('search/activity', array(
|
||||
'values' => array(
|
||||
'search' => $search,
|
||||
'controller' => 'search',
|
||||
'action' => 'activity',
|
||||
),
|
||||
'title' => t('Search in activity stream').($nb_events > 0 ? ' ('.$nb_events.')' : ''),
|
||||
'nb_events' => $nb_events,
|
||||
'events' => $events,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,17 +71,16 @@ class Task extends Base
|
|||
$values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', DateParser::DATE_TIME_FORMAT));
|
||||
|
||||
$this->response->html($this->helper->layout->task('task/show', array(
|
||||
'task' => $task,
|
||||
'project' => $this->project->getById($task['project_id']),
|
||||
'values' => $values,
|
||||
'files' => $this->taskFile->getAllDocuments($task['id']),
|
||||
'images' => $this->taskFile->getAllImages($task['id']),
|
||||
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting()),
|
||||
'subtasks' => $subtasks,
|
||||
'internal_links' => $this->taskLink->getAllGroupedByLabel($task['id']),
|
||||
'external_links' => $this->taskExternalLink->getAll($task['id']),
|
||||
'task' => $task,
|
||||
'values' => $values,
|
||||
'link_label_list' => $this->link->getList(0, false),
|
||||
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id'], true, false, false),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +95,7 @@ class Task extends Base
|
|||
|
||||
$this->response->html($this->helper->layout->task('task/analytics', array(
|
||||
'task' => $task,
|
||||
'project' => $this->project->getById($task['project_id']),
|
||||
'lead_time' => $this->taskAnalytic->getLeadTime($task),
|
||||
'cycle_time' => $this->taskAnalytic->getCycleTime($task),
|
||||
'time_spent_columns' => $this->taskAnalytic->getTimeSpentByColumn($task),
|
||||
|
|
@ -121,6 +121,7 @@ class Task extends Base
|
|||
|
||||
$this->response->html($this->helper->layout->task('task/time_tracking_details', array(
|
||||
'task' => $task,
|
||||
'project' => $this->project->getById($task['project_id']),
|
||||
'subtask_paginator' => $subtask_paginator,
|
||||
)));
|
||||
}
|
||||
|
|
@ -136,6 +137,7 @@ class Task extends Base
|
|||
|
||||
$this->response->html($this->helper->layout->task('task/transitions', array(
|
||||
'task' => $task,
|
||||
'project' => $this->project->getById($task['project_id']),
|
||||
'transitions' => $this->transition->getAllByTask($task['id']),
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\TaskIdExclusionFilter;
|
||||
use Kanboard\Filter\TaskIdFilter;
|
||||
use Kanboard\Filter\TaskProjectsFilter;
|
||||
use Kanboard\Filter\TaskTitleFilter;
|
||||
use Kanboard\Formatter\TaskAutoCompleteFormatter;
|
||||
|
||||
/**
|
||||
* Task Ajax Helper
|
||||
*
|
||||
|
|
@ -11,31 +17,33 @@ namespace Kanboard\Controller;
|
|||
class TaskHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Task autocompletion (Ajax)
|
||||
* Task auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$projects = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId());
|
||||
$exclude_task_id = $this->request->getIntegerParam('exclude_task_id');
|
||||
|
||||
if (empty($projects)) {
|
||||
if (empty($project_ids)) {
|
||||
$this->response->json(array());
|
||||
}
|
||||
|
||||
$filter = $this->taskFilterAutoCompleteFormatter
|
||||
->create()
|
||||
->filterByProjects($projects)
|
||||
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
|
||||
|
||||
// Search by task id or by title
|
||||
if (ctype_digit($search)) {
|
||||
$filter->filterById($search);
|
||||
} else {
|
||||
$filter->filterByTitle($search);
|
||||
}
|
||||
|
||||
$this->response->json($filter->format());
|
||||
$filter = $this->taskQuery->withFilter(new TaskProjectsFilter($project_ids));
|
||||
|
||||
if (! empty($exclude_task_id)) {
|
||||
$filter->withFilter(new TaskIdExclusionFilter(array($exclude_task_id)));
|
||||
}
|
||||
|
||||
if (ctype_digit($search)) {
|
||||
$filter->withFilter(new TaskIdFilter($search));
|
||||
} else {
|
||||
$filter->withFilter(new TaskTitleFilter($search));
|
||||
}
|
||||
|
||||
$this->response->json($filter->format(new TaskAutoCompleteFormatter($this->container)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Filter\UserNameFilter;
|
||||
use Kanboard\Formatter\UserAutoCompleteFormatter;
|
||||
use Kanboard\Model\User as UserModel;
|
||||
|
||||
/**
|
||||
* User Helper
|
||||
*
|
||||
|
|
@ -11,19 +15,20 @@ namespace Kanboard\Controller;
|
|||
class UserHelper extends Base
|
||||
{
|
||||
/**
|
||||
* User autocompletion (Ajax)
|
||||
* User auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function autocomplete()
|
||||
{
|
||||
$search = $this->request->getStringParam('term');
|
||||
$users = $this->userFilterAutoCompleteFormatter->create($search)->filterByUsernameOrByName()->format();
|
||||
$this->response->json($users);
|
||||
$filter = $this->userQuery->withFilter(new UserNameFilter($search));
|
||||
$filter->getQuery()->asc(UserModel::TABLE.'.name')->asc(UserModel::TABLE.'.username');
|
||||
$this->response->json($filter->format(new UserAutoCompleteFormatter($this->container)));
|
||||
}
|
||||
|
||||
/**
|
||||
* User mention autocompletion (Ajax)
|
||||
* User mention auto-completion (Ajax)
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class ActionManager extends Base
|
|||
* List of automatic actions
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
* @var ActionBase[]
|
||||
*/
|
||||
private $actions = array();
|
||||
|
||||
|
|
|
|||
|
|
@ -48,18 +48,11 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Core\User\UserSession $userSession
|
||||
* @property \Kanboard\Core\DateParser $dateParser
|
||||
* @property \Kanboard\Core\Helper $helper
|
||||
* @property \Kanboard\Core\Lexer $lexer
|
||||
* @property \Kanboard\Core\Paginator $paginator
|
||||
* @property \Kanboard\Core\Template $template
|
||||
* @property \Kanboard\Formatter\ProjectGanttFormatter $projectGanttFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterGanttFormatter $taskFilterGanttFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterAutoCompleteFormatter $taskFilterAutoCompleteFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterCalendarFormatter $taskFilterCalendarFormatter
|
||||
* @property \Kanboard\Formatter\TaskFilterICalendarFormatter $taskFilterICalendarFormatter
|
||||
* @property \Kanboard\Formatter\UserFilterAutoCompleteFormatter $userFilterAutoCompleteFormatter
|
||||
* @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter
|
||||
* @property \Kanboard\Model\Action $action
|
||||
* @property \Kanboard\Model\ActionParameter $actionParameter
|
||||
* @property \Kanboard\Model\AvatarFile $avatarFile
|
||||
* @property \Kanboard\Model\Board $board
|
||||
* @property \Kanboard\Model\Category $category
|
||||
* @property \Kanboard\Model\Color $color
|
||||
|
|
@ -84,7 +77,6 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\ProjectMetadata $projectMetadata
|
||||
* @property \Kanboard\Model\ProjectPermission $projectPermission
|
||||
* @property \Kanboard\Model\ProjectUserRole $projectUserRole
|
||||
* @property \Kanboard\Model\projectUserRoleFilter $projectUserRoleFilter
|
||||
* @property \Kanboard\Model\ProjectGroupRole $projectGroupRole
|
||||
* @property \Kanboard\Model\ProjectNotification $projectNotification
|
||||
* @property \Kanboard\Model\ProjectNotificationType $projectNotificationType
|
||||
|
|
@ -98,7 +90,6 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Model\TaskDuplication $taskDuplication
|
||||
* @property \Kanboard\Model\TaskExternalLink $taskExternalLink
|
||||
* @property \Kanboard\Model\TaskFinder $taskFinder
|
||||
* @property \Kanboard\Model\TaskFilter $taskFilter
|
||||
* @property \Kanboard\Model\TaskLink $taskLink
|
||||
* @property \Kanboard\Model\TaskModification $taskModification
|
||||
* @property \Kanboard\Model\TaskPermission $taskPermission
|
||||
|
|
@ -136,6 +127,14 @@ use Pimple\Container;
|
|||
* @property \Kanboard\Export\SubtaskExport $subtaskExport
|
||||
* @property \Kanboard\Export\TaskExport $taskExport
|
||||
* @property \Kanboard\Export\TransitionExport $transitionExport
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $projectGroupRoleQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $projectUserRoleQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $projectActivityQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $userQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $projectQuery
|
||||
* @property \Kanboard\Core\Filter\QueryBuilder $taskQuery
|
||||
* @property \Kanboard\Core\Filter\LexerBuilder $taskLexer
|
||||
* @property \Kanboard\Core\Filter\LexerBuilder $projectActivityLexer
|
||||
* @property \Psr\Log\LoggerInterface $logger
|
||||
* @property \PicoDb\Database $db
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
|
|
@ -172,4 +171,18 @@ abstract class Base
|
|||
{
|
||||
return $this->container[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object instance
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param Container $container
|
||||
* @return static
|
||||
*/
|
||||
public static function getInstance(Container $container)
|
||||
{
|
||||
$self = new static($container);
|
||||
return $self;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,26 +10,6 @@ namespace Kanboard\Core\Cache;
|
|||
*/
|
||||
abstract class Base
|
||||
{
|
||||
/**
|
||||
* Fetch value from cache
|
||||
*
|
||||
* @abstract
|
||||
* @access public
|
||||
* @param string $key
|
||||
* @return mixed Null when not found, cached value otherwise
|
||||
*/
|
||||
abstract public function get($key);
|
||||
|
||||
/**
|
||||
* Save a new value in the cache
|
||||
*
|
||||
* @abstract
|
||||
* @access public
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
abstract public function set($key, $value);
|
||||
|
||||
/**
|
||||
* Proxy cache
|
||||
*
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class ExternalLinkManager extends Base
|
|||
* Registered providers
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
* @var ExternalLinkProviderInterface[]
|
||||
*/
|
||||
private $providers = array();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Criteria Interface
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface CriteriaInterface
|
||||
{
|
||||
/**
|
||||
* Set the Query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withQuery(Table $query);
|
||||
|
||||
/**
|
||||
* Set filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter);
|
||||
|
||||
/**
|
||||
* Apply condition
|
||||
*
|
||||
* @access public
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function apply();
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Filter Interface
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* BaseFilter constructor
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __construct($value = null);
|
||||
|
||||
/**
|
||||
* Set the value
|
||||
*
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function withValue($value);
|
||||
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function withQuery(Table $query);
|
||||
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes();
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply();
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Formatter interface
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
interface FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function withQuery(Table $query);
|
||||
|
||||
/**
|
||||
* Apply formatter
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function format();
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
/**
|
||||
* Lexer
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
/**
|
||||
* Current position
|
||||
*
|
||||
* @access private
|
||||
* @var integer
|
||||
*/
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* Token map
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $tokenMap = array(
|
||||
"/^(\s+)/" => 'T_WHITESPACE',
|
||||
'/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE',
|
||||
'/^(yesterday|tomorrow|today)/' => 'T_DATE',
|
||||
'/^("(.*?)")/' => 'T_STRING',
|
||||
"/^(\w+)/" => 'T_STRING',
|
||||
"/^(#\d+)/" => 'T_STRING',
|
||||
);
|
||||
|
||||
/**
|
||||
* Default token
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $defaultToken = '';
|
||||
|
||||
/**
|
||||
* Add token
|
||||
*
|
||||
* @access public
|
||||
* @param string $regex
|
||||
* @param string $token
|
||||
* @return $this
|
||||
*/
|
||||
public function addToken($regex, $token)
|
||||
{
|
||||
$this->tokenMap = array($regex => $token) + $this->tokenMap;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default token
|
||||
*
|
||||
* @access public
|
||||
* @param string $token
|
||||
* @return $this
|
||||
*/
|
||||
public function setDefaultToken($token)
|
||||
{
|
||||
$this->defaultToken = $token;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize input string
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return array
|
||||
*/
|
||||
public function tokenize($input)
|
||||
{
|
||||
$tokens = array();
|
||||
$this->offset = 0;
|
||||
|
||||
while (isset($input[$this->offset])) {
|
||||
$result = $this->match(substr($input, $this->offset));
|
||||
|
||||
if ($result === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$tokens[] = $result;
|
||||
}
|
||||
|
||||
return $this->map($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a token that match and move the offset
|
||||
*
|
||||
* @access protected
|
||||
* @param string $string
|
||||
* @return array|boolean
|
||||
*/
|
||||
protected function match($string)
|
||||
{
|
||||
foreach ($this->tokenMap as $pattern => $name) {
|
||||
if (preg_match($pattern, $string, $matches)) {
|
||||
$this->offset += strlen($matches[1]);
|
||||
|
||||
return array(
|
||||
'match' => trim($matches[1], '"'),
|
||||
'token' => $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build map of tokens and matches
|
||||
*
|
||||
* @access protected
|
||||
* @param array $tokens
|
||||
* @return array
|
||||
*/
|
||||
protected function map(array $tokens)
|
||||
{
|
||||
$map = array();
|
||||
$leftOver = '';
|
||||
|
||||
while (false !== ($token = current($tokens))) {
|
||||
if ($token['token'] === 'T_STRING' || $token['token'] === 'T_WHITESPACE') {
|
||||
$leftOver .= $token['match'];
|
||||
} else {
|
||||
$next = next($tokens);
|
||||
|
||||
if ($next !== false && in_array($next['token'], array('T_STRING', 'T_DATE'))) {
|
||||
$map[$token['token']][] = $next['match'];
|
||||
}
|
||||
}
|
||||
|
||||
next($tokens);
|
||||
}
|
||||
|
||||
$leftOver = trim($leftOver);
|
||||
|
||||
if ($this->defaultToken !== '' && $leftOver !== '') {
|
||||
$map[$this->defaultToken] = array($leftOver);
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Lexer Builder
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class LexerBuilder
|
||||
{
|
||||
/**
|
||||
* Lexer object
|
||||
*
|
||||
* @access protected
|
||||
* @var Lexer
|
||||
*/
|
||||
protected $lexer;
|
||||
|
||||
/**
|
||||
* Query object
|
||||
*
|
||||
* @access protected
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* List of filters
|
||||
*
|
||||
* @access protected
|
||||
* @var FilterInterface[]
|
||||
*/
|
||||
protected $filters;
|
||||
|
||||
/**
|
||||
* QueryBuilder object
|
||||
*
|
||||
* @access protected
|
||||
* @var QueryBuilder
|
||||
*/
|
||||
protected $queryBuilder;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->lexer = new Lexer;
|
||||
$this->queryBuilder = new QueryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @param bool $default
|
||||
* @return LexerBuilder
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter, $default = false)
|
||||
{
|
||||
$attributes = $filter->getAttributes();
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->filters[$attribute] = $filter;
|
||||
$this->lexer->addToken(sprintf("/^(%s:)/", $attribute), $attribute);
|
||||
|
||||
if ($default) {
|
||||
$this->lexer->setDefaultToken($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return LexerBuilder
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->queryBuilder->withQuery($this->query);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the input and build the query
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function build($input)
|
||||
{
|
||||
$tokens = $this->lexer->tokenize($input);
|
||||
|
||||
foreach ($tokens as $token => $values) {
|
||||
if (isset($this->filters[$token])) {
|
||||
$this->applyFilters($this->filters[$token], $values);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters to the query
|
||||
*
|
||||
* @access protected
|
||||
* @param FilterInterface $filter
|
||||
* @param array $values
|
||||
*/
|
||||
protected function applyFilters(FilterInterface $filter, array $values)
|
||||
{
|
||||
$len = count($values);
|
||||
|
||||
if ($len > 1) {
|
||||
$criteria = new OrCriteria();
|
||||
$criteria->withQuery($this->query);
|
||||
|
||||
foreach ($values as $value) {
|
||||
$currentFilter = clone($filter);
|
||||
$criteria->withFilter($currentFilter->withValue($value));
|
||||
}
|
||||
|
||||
$this->queryBuilder->withCriteria($criteria);
|
||||
} elseif ($len === 1) {
|
||||
$this->queryBuilder->withFilter($filter->withValue($values[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone object with deep copy
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->lexer = clone $this->lexer;
|
||||
$this->query = clone $this->query;
|
||||
$this->queryBuilder = clone $this->queryBuilder;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* OR criteria
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class OrCriteria implements CriteriaInterface
|
||||
{
|
||||
/**
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var FilterInterface[]
|
||||
*/
|
||||
protected $filters = array();
|
||||
|
||||
/**
|
||||
* Set the Query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->filters[] = $filter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply condition
|
||||
*
|
||||
* @access public
|
||||
* @return CriteriaInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->beginOr();
|
||||
|
||||
foreach ($this->filters as $filter) {
|
||||
$filter->withQuery($this->query)->apply();
|
||||
}
|
||||
|
||||
$this->query->closeOr();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Class QueryBuilder
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class QueryBuilder
|
||||
{
|
||||
/**
|
||||
* Query object
|
||||
*
|
||||
* @access protected
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Set the query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a filter
|
||||
*
|
||||
* @access public
|
||||
* @param FilterInterface $filter
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function withFilter(FilterInterface $filter)
|
||||
{
|
||||
$filter->withQuery($this->query)->apply();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a criteria
|
||||
*
|
||||
* @access public
|
||||
* @param CriteriaInterface $criteria
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function withCriteria(CriteriaInterface $criteria)
|
||||
{
|
||||
$criteria->withQuery($this->query)->apply();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a formatter
|
||||
*
|
||||
* @access public
|
||||
* @param FormatterInterface $formatter
|
||||
* @return string|array
|
||||
*/
|
||||
public function format(FormatterInterface $formatter)
|
||||
{
|
||||
return $formatter->withQuery($this->query)->format();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query result as array
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->query->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Query object
|
||||
*
|
||||
* @access public
|
||||
* @return Table
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone object with deep copy
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->query = clone $this->query;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,18 +10,23 @@ use Pimple\Container;
|
|||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @property \Kanboard\Helper\AppHelper $app
|
||||
* @property \Kanboard\Helper\AssetHelper $asset
|
||||
* @property \Kanboard\Helper\DateHelper $dt
|
||||
* @property \Kanboard\Helper\FileHelper $file
|
||||
* @property \Kanboard\Helper\FormHelper $form
|
||||
* @property \Kanboard\Helper\ModelHelper $model
|
||||
* @property \Kanboard\Helper\SubtaskHelper $subtask
|
||||
* @property \Kanboard\Helper\TaskHelper $task
|
||||
* @property \Kanboard\Helper\TextHelper $text
|
||||
* @property \Kanboard\Helper\UrlHelper $url
|
||||
* @property \Kanboard\Helper\UserHelper $user
|
||||
* @property \Kanboard\Helper\LayoutHelper $layout
|
||||
* @property \Kanboard\Helper\AppHelper $app
|
||||
* @property \Kanboard\Helper\AssetHelper $asset
|
||||
* @property \Kanboard\Helper\CalendarHelper $calendar
|
||||
* @property \Kanboard\Helper\DateHelper $dt
|
||||
* @property \Kanboard\Helper\FileHelper $file
|
||||
* @property \Kanboard\Helper\FormHelper $form
|
||||
* @property \Kanboard\Helper\HookHelper $hook
|
||||
* @property \Kanboard\Helper\ICalHelper $ical
|
||||
* @property \Kanboard\Helper\ModelHelper $model
|
||||
* @property \Kanboard\Helper\SubtaskHelper $subtask
|
||||
* @property \Kanboard\Helper\TaskHelper $task
|
||||
* @property \Kanboard\Helper\TextHelper $text
|
||||
* @property \Kanboard\Helper\UrlHelper $url
|
||||
* @property \Kanboard\Helper\UserHelper $user
|
||||
* @property \Kanboard\Helper\LayoutHelper $layout
|
||||
* @property \Kanboard\Helper\ProjectHeaderHelper $projectHeader
|
||||
* @property \Kanboard\Helper\ProjectActivityHelper $projectActivity
|
||||
*/
|
||||
class Helper
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ use Kanboard\Core\Base;
|
|||
*/
|
||||
class OAuth2 extends Base
|
||||
{
|
||||
private $clientId;
|
||||
private $secret;
|
||||
private $callbackUrl;
|
||||
private $authUrl;
|
||||
private $tokenUrl;
|
||||
private $scopes;
|
||||
private $tokenType;
|
||||
private $accessToken;
|
||||
protected $clientId;
|
||||
protected $secret;
|
||||
protected $callbackUrl;
|
||||
protected $authUrl;
|
||||
protected $tokenUrl;
|
||||
protected $scopes;
|
||||
protected $tokenType;
|
||||
protected $accessToken;
|
||||
|
||||
/**
|
||||
* Create OAuth2 service
|
||||
|
|
@ -45,6 +45,33 @@ class OAuth2 extends Base
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate OAuth2 state and return the token value
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
if (! isset($this->sessionStorage->oauthState) || empty($this->sessionStorage->oauthState)) {
|
||||
$this->sessionStorage->oauthState = $this->token->getToken();
|
||||
}
|
||||
|
||||
return $this->sessionStorage->oauthState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the validity of the state (CSRF token)
|
||||
*
|
||||
* @access public
|
||||
* @param string $state
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidateState($state)
|
||||
{
|
||||
return $state === $this->getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authorization url
|
||||
*
|
||||
|
|
@ -58,6 +85,7 @@ class OAuth2 extends Base
|
|||
'client_id' => $this->clientId,
|
||||
'redirect_uri' => $this->callbackUrl,
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
'state' => $this->getState(),
|
||||
);
|
||||
|
||||
return $this->authUrl.'?'.http_build_query($params);
|
||||
|
|
@ -94,6 +122,7 @@ class OAuth2 extends Base
|
|||
'client_secret' => $this->secret,
|
||||
'redirect_uri' => $this->callbackUrl,
|
||||
'grant_type' => 'authorization_code',
|
||||
'state' => $this->getState(),
|
||||
);
|
||||
|
||||
$response = json_decode($this->httpClient->postForm($this->tokenUrl, $params, array('Accept: application/json')), true);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,24 @@ use Kanboard\Core\Csv;
|
|||
*/
|
||||
class Response extends Base
|
||||
{
|
||||
/**
|
||||
* Send headers to cache a resource
|
||||
*
|
||||
* @access public
|
||||
* @param integer $duration
|
||||
* @param string $etag
|
||||
*/
|
||||
public function cache($duration, $etag = '')
|
||||
{
|
||||
header('Pragma: cache');
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $duration) . ' GMT');
|
||||
header('Cache-Control: public, max-age=' . $duration);
|
||||
|
||||
if ($etag) {
|
||||
header('ETag: "' . $etag . '"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send no cache headers
|
||||
*
|
||||
|
|
@ -213,6 +231,20 @@ class Response extends Base
|
|||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a iCal response
|
||||
*
|
||||
* @access public
|
||||
* @param string $data Raw data
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function ical($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
$this->contentType('text/calendar; charset=utf-8');
|
||||
echo $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the security header: Content-Security-Policy
|
||||
*
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Kanboard\Core\Ldap;
|
||||
|
||||
use LogicException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* LDAP Client
|
||||
|
|
@ -20,6 +21,14 @@ class Client
|
|||
*/
|
||||
protected $ldap;
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*
|
||||
* @access private
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Establish LDAP connection
|
||||
*
|
||||
|
|
@ -165,4 +174,39 @@ class Client
|
|||
{
|
||||
return LDAP_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set logger
|
||||
*
|
||||
* @access public
|
||||
* @param LoggerInterface $logger
|
||||
* @return Client
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logger
|
||||
*
|
||||
* @access public
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
public function getLogger()
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a logger is defined
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasLogger()
|
||||
{
|
||||
return $this->logger !== null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ class Query
|
|||
*/
|
||||
public function execute($baseDn, $filter, array $attributes)
|
||||
{
|
||||
if (DEBUG && $this->client->hasLogger()) {
|
||||
$this->client->getLogger()->debug('BaseDN='.$baseDn);
|
||||
$this->client->getLogger()->debug('Filter='.$filter);
|
||||
$this->client->getLogger()->debug('Attributes='.implode(', ', $attributes));
|
||||
}
|
||||
|
||||
$sr = ldap_search($this->client->getConnection(), $baseDn, $filter, $attributes);
|
||||
if ($sr === false) {
|
||||
return $this;
|
||||
|
|
|
|||
|
|
@ -44,8 +44,7 @@ class User
|
|||
*/
|
||||
public static function getUser(Client $client, $username)
|
||||
{
|
||||
$className = get_called_class();
|
||||
$self = new $className(new Query($client));
|
||||
$self = new static(new Query($client));
|
||||
return $self->find($self->getLdapUserPattern($username));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,161 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core;
|
||||
|
||||
/**
|
||||
* Lexer
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
/**
|
||||
* Current position
|
||||
*
|
||||
* @access private
|
||||
* @var integer
|
||||
*/
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* Token map
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $tokenMap = array(
|
||||
"/^(assignee:)/" => 'T_ASSIGNEE',
|
||||
"/^(color:)/" => 'T_COLOR',
|
||||
"/^(due:)/" => 'T_DUE',
|
||||
"/^(updated:)/" => 'T_UPDATED',
|
||||
"/^(modified:)/" => 'T_UPDATED',
|
||||
"/^(created:)/" => 'T_CREATED',
|
||||
"/^(status:)/" => 'T_STATUS',
|
||||
"/^(description:)/" => 'T_DESCRIPTION',
|
||||
"/^(category:)/" => 'T_CATEGORY',
|
||||
"/^(column:)/" => 'T_COLUMN',
|
||||
"/^(project:)/" => 'T_PROJECT',
|
||||
"/^(swimlane:)/" => 'T_SWIMLANE',
|
||||
"/^(ref:)/" => 'T_REFERENCE',
|
||||
"/^(reference:)/" => 'T_REFERENCE',
|
||||
"/^(link:)/" => 'T_LINK',
|
||||
"/^(\s+)/" => 'T_WHITESPACE',
|
||||
'/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE',
|
||||
'/^(yesterday|tomorrow|today)/' => 'T_DATE',
|
||||
'/^("(.*?)")/' => 'T_STRING',
|
||||
"/^(\w+)/" => 'T_STRING',
|
||||
"/^(#\d+)/" => 'T_STRING',
|
||||
);
|
||||
|
||||
/**
|
||||
* Tokenize input string
|
||||
*
|
||||
* @access public
|
||||
* @param string $input
|
||||
* @return array
|
||||
*/
|
||||
public function tokenize($input)
|
||||
{
|
||||
$tokens = array();
|
||||
$this->offset = 0;
|
||||
|
||||
while (isset($input[$this->offset])) {
|
||||
$result = $this->match(substr($input, $this->offset));
|
||||
|
||||
if ($result === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$tokens[] = $result;
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a token that match and move the offset
|
||||
*
|
||||
* @access public
|
||||
* @param string $string
|
||||
* @return array|boolean
|
||||
*/
|
||||
public function match($string)
|
||||
{
|
||||
foreach ($this->tokenMap as $pattern => $name) {
|
||||
if (preg_match($pattern, $string, $matches)) {
|
||||
$this->offset += strlen($matches[1]);
|
||||
|
||||
return array(
|
||||
'match' => trim($matches[1], '"'),
|
||||
'token' => $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the output of tokenizer to be easily parsed by the database filter
|
||||
*
|
||||
* Example: ['T_ASSIGNEE' => ['user1', 'user2'], 'T_TITLE' => 'task title']
|
||||
*
|
||||
* @access public
|
||||
* @param array $tokens
|
||||
* @return array
|
||||
*/
|
||||
public function map(array $tokens)
|
||||
{
|
||||
$map = array(
|
||||
'T_TITLE' => '',
|
||||
);
|
||||
|
||||
while (false !== ($token = current($tokens))) {
|
||||
switch ($token['token']) {
|
||||
case 'T_ASSIGNEE':
|
||||
case 'T_COLOR':
|
||||
case 'T_CATEGORY':
|
||||
case 'T_COLUMN':
|
||||
case 'T_PROJECT':
|
||||
case 'T_SWIMLANE':
|
||||
case 'T_LINK':
|
||||
$next = next($tokens);
|
||||
|
||||
if ($next !== false && $next['token'] === 'T_STRING') {
|
||||
$map[$token['token']][] = $next['match'];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'T_STATUS':
|
||||
case 'T_DUE':
|
||||
case 'T_UPDATED':
|
||||
case 'T_CREATED':
|
||||
case 'T_DESCRIPTION':
|
||||
case 'T_REFERENCE':
|
||||
$next = next($tokens);
|
||||
|
||||
if ($next !== false && ($next['token'] === 'T_DATE' || $next['token'] === 'T_STRING')) {
|
||||
$map[$token['token']] = $next['match'];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$map['T_TITLE'] .= $token['match'];
|
||||
break;
|
||||
}
|
||||
|
||||
next($tokens);
|
||||
}
|
||||
|
||||
$map['T_TITLE'] = trim($map['T_TITLE']);
|
||||
|
||||
if (empty($map['T_TITLE'])) {
|
||||
unset($map['T_TITLE']);
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ namespace Kanboard\Core\Session;
|
|||
* @property bool $boardCollapsed
|
||||
* @property bool $twoFactorBeforeCodeCalled
|
||||
* @property string $twoFactorSecret
|
||||
* @property string $oauthState
|
||||
*/
|
||||
class SessionStorage
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@ namespace Kanboard\Core;
|
|||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @property \Kanboard\Helper\AppHelper $app
|
||||
* @property \Kanboard\Helper\AssetHelper $asset
|
||||
* @property \Kanboard\Helper\DateHelper $dt
|
||||
* @property \Kanboard\Helper\FileHelper $file
|
||||
* @property \Kanboard\Helper\FormHelper $form
|
||||
* @property \Kanboard\Helper\HookHelper $hook
|
||||
* @property \Kanboard\Helper\ModelHelper $model
|
||||
* @property \Kanboard\Helper\SubtaskHelper $subtask
|
||||
* @property \Kanboard\Helper\TaskHelper $task
|
||||
* @property \Kanboard\Helper\TextHelper $text
|
||||
* @property \Kanboard\Helper\UrlHelper $url
|
||||
* @property \Kanboard\Helper\UserHelper $user
|
||||
* @property \Kanboard\Helper\LayoutHelper $layout
|
||||
* @property \Kanboard\Helper\ProjectHeaderHelper $projectHeader
|
||||
*/
|
||||
class Template
|
||||
{
|
||||
|
|
@ -84,25 +99,26 @@ class Template
|
|||
/**
|
||||
* Find template filename
|
||||
*
|
||||
* Core template name: 'task/show'
|
||||
* Plugin template name: 'myplugin:task/show'
|
||||
* Core template: 'task/show' or 'kanboard:task/show'
|
||||
* Plugin template: 'myplugin:task/show'
|
||||
*
|
||||
* @access public
|
||||
* @param string $template_name
|
||||
* @param string $template
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateFile($template_name)
|
||||
public function getTemplateFile($template)
|
||||
{
|
||||
$template_name = isset($this->overrides[$template_name]) ? $this->overrides[$template_name] : $template_name;
|
||||
$plugin = '';
|
||||
$template = isset($this->overrides[$template]) ? $this->overrides[$template] : $template;
|
||||
|
||||
if (strpos($template_name, ':') !== false) {
|
||||
list($plugin, $template) = explode(':', $template_name);
|
||||
$path = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'plugins';
|
||||
$path .= DIRECTORY_SEPARATOR.ucfirst($plugin).DIRECTORY_SEPARATOR.'Template'.DIRECTORY_SEPARATOR.$template.'.php';
|
||||
} else {
|
||||
$path = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Template'.DIRECTORY_SEPARATOR.$template_name.'.php';
|
||||
if (strpos($template, ':') !== false) {
|
||||
list($plugin, $template) = explode(':', $template);
|
||||
}
|
||||
|
||||
return $path;
|
||||
if ($plugin !== 'kanboard' && $plugin !== '') {
|
||||
return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', '..', 'plugins', ucfirst($plugin), 'Template', $template.'.php'));
|
||||
}
|
||||
|
||||
return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'Template', $template.'.php'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core;
|
||||
|
||||
/**
|
||||
* Thumbnail Generator
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Thumbnail
|
||||
{
|
||||
protected $metadata = array();
|
||||
protected $srcImage;
|
||||
protected $dstImage;
|
||||
|
||||
/**
|
||||
* Create a thumbnail from a local file
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $filename
|
||||
* @return Thumbnail
|
||||
*/
|
||||
public static function createFromFile($filename)
|
||||
{
|
||||
$self = new static();
|
||||
$self->fromFile($filename);
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a thumbnail from a string
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $blob
|
||||
* @return Thumbnail
|
||||
*/
|
||||
public static function createFromString($blob)
|
||||
{
|
||||
$self = new static();
|
||||
$self->fromString($blob);
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the local image file in memory with GD
|
||||
*
|
||||
* @access public
|
||||
* @param string $filename
|
||||
* @return Thumbnail
|
||||
*/
|
||||
public function fromFile($filename)
|
||||
{
|
||||
$this->metadata = getimagesize($filename);
|
||||
$this->srcImage = imagecreatefromstring(file_get_contents($filename));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the image blob in memory with GD
|
||||
*
|
||||
* @access public
|
||||
* @param string $blob
|
||||
* @return Thumbnail
|
||||
*/
|
||||
public function fromString($blob)
|
||||
{
|
||||
if (!function_exists('getimagesizefromstring')) {
|
||||
$uri = 'data://application/octet-stream;base64,' . base64_encode($blob);
|
||||
$this->metadata = getimagesize($uri);
|
||||
} else {
|
||||
$this->metadata = getimagesizefromstring($blob);
|
||||
}
|
||||
|
||||
$this->srcImage = imagecreatefromstring($blob);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the image
|
||||
*
|
||||
* @access public
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return Thumbnail
|
||||
*/
|
||||
public function resize($width = 250, $height = 100)
|
||||
{
|
||||
$srcWidth = $this->metadata[0];
|
||||
$srcHeight = $this->metadata[1];
|
||||
$dstX = 0;
|
||||
$dstY = 0;
|
||||
|
||||
if ($width == 0 && $height == 0) {
|
||||
$width = 100;
|
||||
$height = 100;
|
||||
}
|
||||
|
||||
if ($width > 0 && $height == 0) {
|
||||
$dstWidth = $width;
|
||||
$dstHeight = floor($srcHeight * ($width / $srcWidth));
|
||||
$this->dstImage = imagecreatetruecolor($dstWidth, $dstHeight);
|
||||
} elseif ($width == 0 && $height > 0) {
|
||||
$dstWidth = floor($srcWidth * ($height / $srcHeight));
|
||||
$dstHeight = $height;
|
||||
$this->dstImage = imagecreatetruecolor($dstWidth, $dstHeight);
|
||||
} else {
|
||||
$srcRatio = $srcWidth / $srcHeight;
|
||||
$resizeRatio = $width / $height;
|
||||
|
||||
if ($srcRatio <= $resizeRatio) {
|
||||
$dstWidth = $width;
|
||||
$dstHeight = floor($srcHeight * ($width / $srcWidth));
|
||||
$dstY = ($dstHeight - $height) / 2 * (-1);
|
||||
} else {
|
||||
$dstWidth = floor($srcWidth * ($height / $srcHeight));
|
||||
$dstHeight = $height;
|
||||
$dstX = ($dstWidth - $width) / 2 * (-1);
|
||||
}
|
||||
|
||||
$this->dstImage = imagecreatetruecolor($width, $height);
|
||||
}
|
||||
|
||||
imagecopyresampled($this->dstImage, $this->srcImage, $dstX, $dstY, 0, 0, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the thumbnail to a local file
|
||||
*
|
||||
* @access public
|
||||
* @param string $filename
|
||||
* @return Thumbnail
|
||||
*/
|
||||
public function toFile($filename)
|
||||
{
|
||||
imagejpeg($this->dstImage, $filename);
|
||||
imagedestroy($this->dstImage);
|
||||
imagedestroy($this->srcImage);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the thumbnail as a string
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function toString()
|
||||
{
|
||||
ob_start();
|
||||
imagejpeg($this->dstImage, null);
|
||||
imagedestroy($this->dstImage);
|
||||
imagedestroy($this->srcImage);
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the thumbnail directly to the browser or stdout
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function toOutput()
|
||||
{
|
||||
imagejpeg($this->dstImage, null);
|
||||
imagedestroy($this->dstImage);
|
||||
imagedestroy($this->srcImage);
|
||||
}
|
||||
}
|
||||
|
|
@ -75,78 +75,4 @@ class Tool
|
|||
|
||||
return $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a jpeg thumbnail from an image
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $src_file Source file image
|
||||
* @param string $dst_file Destination file image
|
||||
* @param integer $resize_width Desired image width
|
||||
* @param integer $resize_height Desired image height
|
||||
*/
|
||||
public static function generateThumbnail($src_file, $dst_file, $resize_width = 250, $resize_height = 100)
|
||||
{
|
||||
$metadata = getimagesize($src_file);
|
||||
$src_width = $metadata[0];
|
||||
$src_height = $metadata[1];
|
||||
$dst_y = 0;
|
||||
$dst_x = 0;
|
||||
|
||||
if (empty($metadata['mime'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($resize_width == 0 && $resize_height == 0) {
|
||||
$resize_width = 100;
|
||||
$resize_height = 100;
|
||||
}
|
||||
|
||||
if ($resize_width > 0 && $resize_height == 0) {
|
||||
$dst_width = $resize_width;
|
||||
$dst_height = floor($src_height * ($resize_width / $src_width));
|
||||
$dst_image = imagecreatetruecolor($dst_width, $dst_height);
|
||||
} elseif ($resize_width == 0 && $resize_height > 0) {
|
||||
$dst_width = floor($src_width * ($resize_height / $src_height));
|
||||
$dst_height = $resize_height;
|
||||
$dst_image = imagecreatetruecolor($dst_width, $dst_height);
|
||||
} else {
|
||||
$src_ratio = $src_width / $src_height;
|
||||
$resize_ratio = $resize_width / $resize_height;
|
||||
|
||||
if ($src_ratio <= $resize_ratio) {
|
||||
$dst_width = $resize_width;
|
||||
$dst_height = floor($src_height * ($resize_width / $src_width));
|
||||
|
||||
$dst_y = ($dst_height - $resize_height) / 2 * (-1);
|
||||
} else {
|
||||
$dst_width = floor($src_width * ($resize_height / $src_height));
|
||||
$dst_height = $resize_height;
|
||||
|
||||
$dst_x = ($dst_width - $resize_width) / 2 * (-1);
|
||||
}
|
||||
|
||||
$dst_image = imagecreatetruecolor($resize_width, $resize_height);
|
||||
}
|
||||
|
||||
switch ($metadata['mime']) {
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
$src_image = imagecreatefromjpeg($src_file);
|
||||
break;
|
||||
case 'image/png':
|
||||
$src_image = imagecreatefrompng($src_file);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$src_image = imagecreatefromgif($src_file);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
|
||||
imagejpeg($dst_image, $dst_file);
|
||||
imagedestroy($dst_image);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,23 +32,25 @@ class AvatarManager
|
|||
}
|
||||
|
||||
/**
|
||||
* Render avatar html element
|
||||
* Render avatar HTML element
|
||||
*
|
||||
* @access public
|
||||
* @param string $user_id
|
||||
* @param string $username
|
||||
* @param string $name
|
||||
* @param string $email
|
||||
* @param string $avatar_path
|
||||
* @param int $size
|
||||
* @return string
|
||||
*/
|
||||
public function render($user_id, $username, $name, $email, $size)
|
||||
public function render($user_id, $username, $name, $email, $avatar_path, $size)
|
||||
{
|
||||
$user = array(
|
||||
'id' => $user_id,
|
||||
'username' => $username,
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'avatar_path' => $avatar_path,
|
||||
);
|
||||
|
||||
krsort($this->providers);
|
||||
|
|
@ -80,6 +82,7 @@ class AvatarManager
|
|||
'username' => '',
|
||||
'name' => '?',
|
||||
'email' => '',
|
||||
'avatar_path' => '',
|
||||
);
|
||||
|
||||
return $provider->render($user, $size);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,19 @@ use Kanboard\Core\Security\Role;
|
|||
*/
|
||||
class UserSession extends Base
|
||||
{
|
||||
/**
|
||||
* Refresh current session if necessary
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id
|
||||
*/
|
||||
public function refresh($user_id)
|
||||
{
|
||||
if ($this->getId() == $user_id) {
|
||||
$this->initialize($this->user->getById($user_id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user session
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\DateParser;
|
||||
|
||||
/**
|
||||
* Base date filter class
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class BaseDateFilter extends BaseFilter
|
||||
{
|
||||
/**
|
||||
* DateParser object
|
||||
*
|
||||
* @access protected
|
||||
* @var DateParser
|
||||
*/
|
||||
protected $dateParser;
|
||||
|
||||
/**
|
||||
* Set DateParser object
|
||||
*
|
||||
* @access public
|
||||
* @param DateParser $dateParser
|
||||
* @return $this
|
||||
*/
|
||||
public function setDateParser(DateParser $dateParser)
|
||||
{
|
||||
$this->dateParser = $dateParser;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse operator in the input string
|
||||
*
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function parseOperator()
|
||||
{
|
||||
$operators = array(
|
||||
'<=' => 'lte',
|
||||
'>=' => 'gte',
|
||||
'<' => 'lt',
|
||||
'>' => 'gt',
|
||||
);
|
||||
|
||||
foreach ($operators as $operator => $method) {
|
||||
if (strpos($this->value, $operator) === 0) {
|
||||
$this->value = substr($this->value, strlen($operator));
|
||||
return $method;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a date filter
|
||||
*
|
||||
* @access protected
|
||||
* @param string $field
|
||||
*/
|
||||
protected function applyDateFilter($field)
|
||||
{
|
||||
$method = $this->parseOperator();
|
||||
$timestamp = $this->dateParser->getTimestampFromIsoFormat($this->value);
|
||||
|
||||
if ($method !== '') {
|
||||
$this->query->$method($field, $this->getTimestampFromOperator($method, $timestamp));
|
||||
} else {
|
||||
$this->query->gte($field, $timestamp);
|
||||
$this->query->lte($field, $timestamp + 86399);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timestamp from the operator
|
||||
*
|
||||
* @access public
|
||||
* @param string $method
|
||||
* @param integer $timestamp
|
||||
* @return integer
|
||||
*/
|
||||
protected function getTimestampFromOperator($method, $timestamp)
|
||||
{
|
||||
switch ($method) {
|
||||
case 'lte':
|
||||
return $timestamp + 86399;
|
||||
case 'lt':
|
||||
return $timestamp;
|
||||
case 'gte':
|
||||
return $timestamp;
|
||||
case 'gt':
|
||||
return $timestamp + 86400;
|
||||
}
|
||||
|
||||
return $timestamp;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Base filter class
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class BaseFilter
|
||||
{
|
||||
/**
|
||||
* @var Table
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* BaseFilter constructor
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __construct($value = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object instance
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param mixed $value
|
||||
* @return static
|
||||
*/
|
||||
public static function getInstance($value = null)
|
||||
{
|
||||
$self = new static($value);
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set query
|
||||
*
|
||||
* @access public
|
||||
* @param Table $query
|
||||
* @return \Kanboard\Core\Filter\FilterInterface
|
||||
*/
|
||||
public function withQuery(Table $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value
|
||||
*
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @return \Kanboard\Core\Filter\FilterInterface
|
||||
*/
|
||||
public function withValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\ProjectActivity;
|
||||
|
||||
/**
|
||||
* Filter activity events by creation date
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityCreationDateFilter extends BaseDateFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->applyDateFilter(ProjectActivity::TABLE.'.date_creation');
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\ProjectActivity;
|
||||
|
||||
/**
|
||||
* Filter activity events by creator
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityCreatorFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Current user id
|
||||
*
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $currentUserId = 0;
|
||||
|
||||
/**
|
||||
* Set current user id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return TaskAssigneeFilter
|
||||
*/
|
||||
public function setCurrentUserId($userId)
|
||||
{
|
||||
$this->currentUserId = $userId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('creator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if ($this->value === 'me') {
|
||||
$this->query->eq(ProjectActivity::TABLE . '.creator_id', $this->currentUserId);
|
||||
} else {
|
||||
$this->query->beginOr();
|
||||
$this->query->ilike('uc.username', '%'.$this->value.'%');
|
||||
$this->query->ilike('uc.name', '%'.$this->value.'%');
|
||||
$this->query->closeOr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\ProjectActivity;
|
||||
|
||||
/**
|
||||
* Filter activity events by projectId
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityProjectIdFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('project_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->eq(ProjectActivity::TABLE.'.project_id', $this->value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\ProjectActivity;
|
||||
|
||||
/**
|
||||
* Filter activity events by projectIds
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityProjectIdsFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('projects');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (empty($this->value)) {
|
||||
$this->query->eq(ProjectActivity::TABLE.'.project_id', 0);
|
||||
} else {
|
||||
$this->query->in(ProjectActivity::TABLE.'.project_id', $this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Project;
|
||||
|
||||
/**
|
||||
* Filter activity events by project name
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityProjectNameFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->ilike(Project::TABLE.'.name', '%'.$this->value.'%');
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\ProjectActivity;
|
||||
|
||||
/**
|
||||
* Filter activity events by taskId
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityTaskIdFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('task_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->eq(ProjectActivity::TABLE.'.task_id', $this->value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter activity events by task status
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityTaskStatusFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if ($this->value === 'open') {
|
||||
$this->query->eq(Task::TABLE.'.is_active', Task::STATUS_OPEN);
|
||||
} elseif ($this->value === 'closed') {
|
||||
$this->query->eq(Task::TABLE.'.is_active', Task::STATUS_CLOSED);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Filter activity events by task title
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityTaskTitleFilter extends TaskTitleFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('title');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\ProjectGroupRole;
|
||||
|
||||
/**
|
||||
* Filter ProjectGroupRole users by project
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectGroupRoleProjectFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->eq(ProjectGroupRole::TABLE.'.project_id', $this->value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\GroupMember;
|
||||
use Kanboard\Model\ProjectGroupRole;
|
||||
use Kanboard\Model\User;
|
||||
|
||||
/**
|
||||
* Filter ProjectGroupRole users by username
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectGroupRoleUsernameFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query
|
||||
->join(GroupMember::TABLE, 'group_id', 'group_id', ProjectGroupRole::TABLE)
|
||||
->join(User::TABLE, 'id', 'user_id', GroupMember::TABLE)
|
||||
->ilike(User::TABLE.'.username', $this->value.'%');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Project;
|
||||
|
||||
/**
|
||||
* Filter project by ids
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectIdsFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('project_ids');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (empty($this->value)) {
|
||||
$this->query->eq(Project::TABLE.'.id', 0);
|
||||
} else {
|
||||
$this->query->in(Project::TABLE.'.id', $this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Project;
|
||||
|
||||
/**
|
||||
* Filter project by status
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectStatusFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (is_int($this->value) || ctype_digit($this->value)) {
|
||||
$this->query->eq(Project::TABLE.'.is_active', $this->value);
|
||||
} elseif ($this->value === 'inactive' || $this->value === 'closed' || $this->value === 'disabled') {
|
||||
$this->query->eq(Project::TABLE.'.is_active', 0);
|
||||
} else {
|
||||
$this->query->eq(Project::TABLE.'.is_active', 1);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Project;
|
||||
|
||||
/**
|
||||
* Filter project by type
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectTypeFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (is_int($this->value) || ctype_digit($this->value)) {
|
||||
$this->query->eq(Project::TABLE.'.is_private', $this->value);
|
||||
} elseif ($this->value === 'private') {
|
||||
$this->query->eq(Project::TABLE.'.is_private', Project::TYPE_PRIVATE);
|
||||
} else {
|
||||
$this->query->eq(Project::TABLE.'.is_private', Project::TYPE_TEAM);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\ProjectUserRole;
|
||||
|
||||
/**
|
||||
* Filter ProjectUserRole users by project
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectUserRoleProjectFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->eq(ProjectUserRole::TABLE.'.project_id', $this->value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\User;
|
||||
|
||||
/**
|
||||
* Filter ProjectUserRole users by username
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectUserRoleUsernameFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query
|
||||
->join(User::TABLE, 'id', 'user_id')
|
||||
->ilike(User::TABLE.'.username', $this->value.'%');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
use Kanboard\Model\User;
|
||||
|
||||
/**
|
||||
* Filter tasks by assignee
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssigneeFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Current user id
|
||||
*
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $currentUserId = 0;
|
||||
|
||||
/**
|
||||
* Set current user id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return TaskAssigneeFilter
|
||||
*/
|
||||
public function setCurrentUserId($userId)
|
||||
{
|
||||
$this->currentUserId = $userId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('assignee');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (is_int($this->value) || ctype_digit($this->value)) {
|
||||
$this->query->eq(Task::TABLE.'.owner_id', $this->value);
|
||||
} else {
|
||||
switch ($this->value) {
|
||||
case 'me':
|
||||
$this->query->eq(Task::TABLE.'.owner_id', $this->currentUserId);
|
||||
break;
|
||||
case 'nobody':
|
||||
$this->query->eq(Task::TABLE.'.owner_id', 0);
|
||||
break;
|
||||
default:
|
||||
$this->query->beginOr();
|
||||
$this->query->ilike(User::TABLE.'.username', '%'.$this->value.'%');
|
||||
$this->query->ilike(User::TABLE.'.name', '%'.$this->value.'%');
|
||||
$this->query->closeOr();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Category;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by category
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskCategoryFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (is_int($this->value) || ctype_digit($this->value)) {
|
||||
$this->query->eq(Task::TABLE.'.category_id', $this->value);
|
||||
} elseif ($this->value === 'none') {
|
||||
$this->query->eq(Task::TABLE.'.category_id', 0);
|
||||
} else {
|
||||
$this->query->eq(Category::TABLE.'.name', $this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Color;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by color
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskColorFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Color object
|
||||
*
|
||||
* @access private
|
||||
* @var Color
|
||||
*/
|
||||
private $colorModel;
|
||||
|
||||
/**
|
||||
* Set color model object
|
||||
*
|
||||
* @access public
|
||||
* @param Color $colorModel
|
||||
* @return TaskColorFilter
|
||||
*/
|
||||
public function setColorModel(Color $colorModel)
|
||||
{
|
||||
$this->colorModel = $colorModel;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('color', 'colour');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->eq(Task::TABLE.'.color_id', $this->colorModel->find($this->value));
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Column;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by column
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskColumnFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('column');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (is_int($this->value) || ctype_digit($this->value)) {
|
||||
$this->query->eq(Task::TABLE.'.column_id', $this->value);
|
||||
} else {
|
||||
$this->query->eq(Column::TABLE.'.title', $this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Comment;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by comment
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskCommentFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('comment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->ilike(Comment::TABLE.'.comment', '%'.$this->value.'%');
|
||||
$this->query->join(Comment::TABLE, 'task_id', 'id', Task::TABLE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by completion date
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskCompletionDateFilter extends BaseDateFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('completed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->applyDateFilter(Task::TABLE.'.date_completed');
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by creation date
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskCreationDateFilter extends BaseDateFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->applyDateFilter(Task::TABLE.'.date_creation');
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by creator
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskCreatorFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Current user id
|
||||
*
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $currentUserId = 0;
|
||||
|
||||
/**
|
||||
* Set current user id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userId
|
||||
* @return TaskAssigneeFilter
|
||||
*/
|
||||
public function setCurrentUserId($userId)
|
||||
{
|
||||
$this->currentUserId = $userId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('creator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
if (is_int($this->value) || ctype_digit($this->value)) {
|
||||
$this->query->eq(Task::TABLE.'.creator_id', $this->value);
|
||||
} else {
|
||||
switch ($this->value) {
|
||||
case 'me':
|
||||
$this->query->eq(Task::TABLE.'.creator_id', $this->currentUserId);
|
||||
break;
|
||||
case 'nobody':
|
||||
$this->query->eq(Task::TABLE.'.creator_id', 0);
|
||||
break;
|
||||
default:
|
||||
$this->query->beginOr();
|
||||
$this->query->ilike('uc.username', '%'.$this->value.'%');
|
||||
$this->query->ilike('uc.name', '%'.$this->value.'%');
|
||||
$this->query->closeOr();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by description
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskDescriptionFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('description', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->ilike(Task::TABLE.'.description', '%'.$this->value.'%');
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by due date
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskDueDateFilter extends BaseDateFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('due');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->neq(Task::TABLE.'.date_due', 0);
|
||||
$this->query->notNull(Task::TABLE.'.date_due');
|
||||
$this->applyDateFilter(Task::TABLE.'.date_due');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by due date range
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskDueDateRangeFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->gte(Task::TABLE.'.date_due', is_numeric($this->value[0]) ? $this->value[0] : strtotime($this->value[0]));
|
||||
$this->query->lte(Task::TABLE.'.date_due', is_numeric($this->value[1]) ? $this->value[1] : strtotime($this->value[1]));
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Exclude task ids
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskIdExclusionFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('exclude');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->notin(Task::TABLE.'.id', $this->value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Task;
|
||||
|
||||
/**
|
||||
* Filter tasks by id
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskIdFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return FilterInterface
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$this->query->eq(Task::TABLE.'.id', $this->value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Filter;
|
||||
|
||||
use Kanboard\Core\Filter\FilterInterface;
|
||||
use Kanboard\Model\Link;
|
||||
use Kanboard\Model\Task;
|
||||
use Kanboard\Model\TaskLink;
|
||||
use PicoDb\Database;
|
||||
use PicoDb\Table;
|
||||
|
||||
/**
|
||||
* Filter tasks by link name
|
||||
*
|
||||
* @package filter
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskLinkFilter extends BaseFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Database object
|
||||
*
|
||||
* @access private
|
||||
* @var Database
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* Set database object
|
||||
*
|
||||
* @access public
|
||||
* @param Database $db
|
||||
* @return TaskLinkFilter
|
||||
*/
|
||||
public function setDatabase(Database $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search attribute
|
||||
*
|
||||
* @access public
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return array('link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function apply()
|
||||
{
|
||||
$task_ids = $this->getSubQuery()->findAllByColumn('task_id');
|
||||
|
||||
if (! empty($task_ids)) {
|
||||
$this->query->in(Task::TABLE.'.id', $task_ids);
|
||||
} else {
|
||||
$this->query->eq(Task::TABLE.'.id', 0); // No match
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subquery
|
||||
*
|
||||
* @access protected
|
||||
* @return Table
|
||||
*/
|
||||
protected function getSubQuery()
|
||||
{
|
||||
return $this->db->table(TaskLink::TABLE)
|
||||
->columns(
|
||||
TaskLink::TABLE.'.task_id',
|
||||
Link::TABLE.'.label'
|
||||
)
|
||||
->join(Link::TABLE, 'id', 'link_id', TaskLink::TABLE)
|
||||
->ilike(Link::TABLE.'.label', $this->value);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue