major refactoring and cleanup

This commit is contained in:
Kevin Papst
2016-10-23 00:12:32 +02:00
parent 5a85b1d37e
commit c492d30be3
70 changed files with 1304 additions and 4027 deletions

View File

@@ -1,22 +1,29 @@
# Kimai - Time Tracking Application
# Kimai v2 - Time Tracking
Kimai v2 - the reloaded open source Time-Tracking application, based on Symfony and powered by PHP.
Kimai v2 - the reloaded open source Time-Tracking application.
[![Build Status](https://travis-ci.org/kevinpapst/kimai2.svg?branch=master)](https://travis-ci.org/kevinpapst/kimai2)
Requirements
------------
## Introduction
This is the reloaded version of the open source time-tracking application [Kimai](http://www.kimai.org).
It is based on the following components:
- Symfony Framework 2.x
- AdminLTE Control Panel Template (through the AdminThemeBundle)
## Requirements
* PHP 5.5.9 or higher;
* One PHP extension of PDO-SQLite and/or PDO-MySQL enabled;
* and the [usual Symfony application requirements](http://symfony.com/doc/current/reference/requirements.html).
* bower (through npm)
If unsure about meeting these requirements, download the demo application and
browse the `http://localhost:8000/config.php` script to get more detailed
information.
Installation
------------
## Installation
[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy)
@@ -29,6 +36,13 @@ $ cd kimai2/
$ composer install
```
Lets prepare the environment
```bash
$ php bin/console assets:install --symlink
$ php bin/console avanzu:admin:fetch-vendor
```
This was the basic task of the installation. If you have not yet setup a database, you
can create it and import example data by executing these commands:
```bash
@@ -44,8 +58,7 @@ If you have imported the example data, you can login with two accounts:
- Username: *tony_teamlead* / Password: *kitten* / Role: Teamlead
- Username: *anna_admin* / Password: *kitten* / Role: Administrator
Usage
-----
## Usage
There is no need to configure a virtual host in your web server to access the application.
Just use the built-in web server:
@@ -65,3 +78,10 @@ terminal.
> Kimai, configure it to point at the `web/` directory of the project.
> For more details, see:
> http://symfony.com/doc/current/cookbook/configuration/web_server_configuration.html
## Troubleshooting
Cannot see any assets (like images) and/or missing styles? Try executing:
```bash
$ php bin/console assets:install --symlink
```

View File

@@ -32,9 +32,9 @@ class AppKernel extends Kernel
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
new AppBundle\AppBundle(),
new TimesheetBundle\TimesheetBundle(),
new Avanzu\AdminThemeBundle\AvanzuAdminThemeBundle(),
];
// Bundles only used for development or unit and functional tests

View File

@@ -19,7 +19,7 @@
{{ 'http_error.description'|trans({ '%status_code%': status_code }) }}
</p>
<p>
{{ 'http_error.suggestion'|trans({ '%url%': path('blog_index') })|raw }}
{{ 'http_error.suggestion'|trans({ '%url%': path('homepage') })|raw }}
</p>
{% endblock %}

View File

@@ -19,7 +19,7 @@
{{ 'http_error_404.description'|trans }}
</p>
<p>
{{ 'http_error_404.suggestion'|trans({ '%url%': path('blog_index') })|raw }}
{{ 'http_error_404.suggestion'|trans({ '%url%': path('homepage') })|raw }}
</p>
{% endblock %}

View File

@@ -19,7 +19,7 @@
{{ 'http_error_500.description'|trans }}
</p>
<p>
{{ 'http_error_500.suggestion'|trans({ '%url%': path('blog_index') })|raw }}
{{ 'http_error_500.suggestion'|trans({ '%url%': path('homepage') })|raw }}
</p>
{% endblock %}

View File

@@ -2,6 +2,52 @@
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file date="2016-10-19T21:46:45Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<body>
<trans-unit id="browser.title">
<source>browser.title</source>
<target>Kimai - Time Tracking</target>
</trans-unit>
<trans-unit id="app.title">
<source>app.title</source>
<target>Kimai</target>
</trans-unit>
<trans-unit id="http_error.name">
<source>http_error.name</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error.description">
<source>http_error.description</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error.suggestion">
<source>http_error.suggestion</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error_403.description">
<source>http_error_403.description</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error_403.suggestion">
<source>http_error_403.suggestion</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error_404.description">
<source>http_error_404.description</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error_404.suggestion">
<source>http_error_404.suggestion</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error_405.description">
<source>http_error_405.description</source>
<target>Error</target>
</trans-unit>
<trans-unit id="http_error_405.suggestion">
<source>http_error_405.suggestion</source>
<target>Error</target>
</trans-unit>
<trans-unit id="action.browse_admin">
<source>action.browse_admin</source>
<target>Zum Backend</target>
@@ -38,10 +84,6 @@
<source>menu.logout</source>
<target>Abmelden</target>
</trans-unit>
<trans-unit id="menu.homepage">
<source>menu.homepage</source>
<target>Blog</target>
</trans-unit>
<trans-unit id="menu.admin">
<source>menu.admin</source>
<target>Backend</target>

View File

@@ -1,25 +0,0 @@
{#
By default, forms enable client-side validation. This means that you can't
test the server-side validation errors from the browser. To temporarily
disable this validation, add the 'novalidate' attribute:
{{ form_start(form, { attr: { novalidate: 'novalidate' } }) }}
#}
{% if show_confirmation|default(false) %}
{% set attr = {'data-confirmation': 'true'} %}
{{ include('blog/_delete_post_confirmation.html.twig') }}
{% endif %}
{{ form_start(form, { attr: attr|default({}) }) }}
{{ form_widget(form) }}
<input type="submit" value="{{ button_label|default('label.create_post'|trans) }}"
class="{{ button_css|default("btn btn-primary") }}" />
{% if include_back_to_home_link|default(false) %}
<a href="{{ path('admin_post_index') }}" class="btn btn-link">
{{ 'action.back_to_list'|trans }}
</a>
{% endif %}
{{ form_end(form) }}

View File

@@ -1,26 +0,0 @@
{% extends 'admin/layout.html.twig' %}
{% block body_id 'admin_post_edit' %}
{% block main %}
<h1>{{ 'title.edit_post'|trans({'%id%': post.id}) }}</h1>
{{ include('admin/blog/_form.html.twig', {
form: edit_form,
button_label: 'action.save'|trans,
include_back_to_home_link: true,
}, with_context = false) }}
{% endblock %}
{% block sidebar %}
<div class="section actions">
{{ include('admin/blog/_form.html.twig', {
form: delete_form,
button_label: 'action.delete_post'|trans,
button_css: 'btn btn-lg btn-block btn-danger',
show_confirmation: true,
}, with_context = false) }}
</div>
{{ parent() }}
{% endblock %}

View File

@@ -1,57 +0,0 @@
{% extends 'admin/layout.html.twig' %}
{% block body_id 'admin_post_index' %}
{% block main %}
<h1>{{ 'title.post_list'|trans }}</h1>
<table class="table table-striped">
<thead>
<tr>
<th>{{ 'label.title'|trans }}</th>
<th><i class="fa fa-user"></i> {{ 'label.author'|trans }}</th>
<th><i class="fa fa-calendar"></i> {{ 'label.published_at'|trans }}</th>
<th><i class="fa fa-cogs"></i> {{ 'label.actions'|trans }}</th>
</tr>
</thead>
<tbody>
{% for post in posts %}
<tr>
<td>{{ post.title }}</td>
<td>{{ post.authorEmail }}</td>
{# it's not mandatory to set the timezone in localizeddate(). This is done to
avoid errors when the 'intl' PHP extension is not available and the application
is forced to use the limited "intl polyfill", which only supports UTC and GMT #}
<td>{% if post.publishedAt %}{{ post.publishedAt|localizeddate('short', 'short', null, 'UTC') }}{% endif %}</td>
<td>
<div class="item-actions">
<a href="{{ path('admin_post_show', { id: post.id }) }}" class="btn btn-sm btn-default">
{{ 'action.show'|trans }}
</a>
{% if post.isAuthor(app.user) %}
<a href="{{ path('admin_post_edit', { id: post.id }) }}" class="btn btn-sm btn-primary">
<i class="fa fa-edit"></i> {{ 'action.edit'|trans }}
</a>
{% endif %}
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="4" align="center">{{ 'post.no_posts_found'|trans }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% block sidebar %}
<div class="section actions">
<a href="{{ path('admin_post_new') }}" class="btn btn-lg btn-block btn-success">
<i class="fa fa-plus"></i> {{ 'action.create_post'|trans }}
</a>
</div>
{{ parent() }}
{% endblock %}

View File

@@ -1,27 +0,0 @@
{% extends 'admin/layout.html.twig' %}
{% block body_id 'admin_post_new' %}
{% block main %}
<h1>{{ 'title.post_new'|trans }}</h1>
{{ form_start(form) }}
{{ form_row(form.title) }}
{{ form_row(form.summary) }}
{{ form_row(form.content) }}
{{ form_row(form.authorEmail) }}
{{ form_row(form.publishedAt) }}
<input type="submit" value="{{ 'label.create_post'|trans }}" class="btn btn-primary" />
{{ form_widget(form.saveAndCreateNew, { label: 'label.save_and_create_new', attr: { class: 'btn btn-primary' } }) }}
<a href="{{ path('admin_post_index') }}" class="btn btn-link">
{{ 'action.back_to_list'|trans }}
</a>
{{ form_end(form) }}
{% endblock %}
{% block sidebar %}
{{ parent() }}
{% endblock %}

View File

@@ -1,48 +0,0 @@
{% extends 'admin/layout.html.twig' %}
{% block body_id 'admin_post_show' %}
{% block main %}
<h1>{{ post.title }}</h1>
<table class="table">
<tbody>
<tr>
<th>{{ 'label.summary'|trans }}</th>
<td>{{ post.summary|md2html }}</td>
</tr>
<tr>
<th>{{ 'label.content'|trans }}</th>
<td>{{ post.content|md2html }}</td>
</tr>
<tr>
<th>{{ 'label.author'|trans }}</th>
<td><p>{{ post.authorEmail }}</p></td>
</tr>
<tr>
<th>{{ 'label.published_at'|trans }}</th>
{# it's not mandatory to set the timezone in localizeddate(). This is done to
avoid errors when the 'intl' PHP extension is not available and the application
is forced to use the limited "intl polyfill", which only supports UTC and GMT #}
<td><p>{{ post.publishedAt|localizeddate('long', 'medium', null, 'UTC') }}</p></td>
</tr>
</tbody>
</table>
{% endblock %}
{% block sidebar %}
<div class="section">
<a href="{{ path('admin_post_edit', { id: post.id }) }}" class="btn btn-lg btn-block btn-success">
<i class="fa fa-edit"></i> {{ 'action.edit_contents'|trans }}
</a>
</div>
<div class="section">
{{ include('admin/blog/_form.html.twig', {
form: delete_form,
button_label: 'action.delete_post'|trans,
button_css: 'btn btn-lg btn-block btn-danger',
show_confirmation: true,
}, with_context = false) }}
</div>
{% endblock %}

View File

@@ -1,9 +0,0 @@
{#
This is the base template of the all backend pages.
See http://symfony.com/doc/current/book/templating.html#template-inheritance-and-layouts
#}
{% extends 'base.html.twig' %}
{% block header_navigation_links %}
{{ knp_menu_render('admin', {'template': 'menu/admin_menu.html.twig'}) }}
{% endblock %}

View File

@@ -1,122 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{% block title %}Symfony Demo application{% endblock %}</title>
{% extends 'AvanzuAdminThemeBundle:layout:base-layout.html.twig' %}
{% block stylesheets %}
{# uncomment the following lines to compile SCSS assets with Assetic
{% block page_content %}
{% block main %}
{% endblock %}
{% endblock %}
{% stylesheets filter="scssphp" output="css/app.css"
"%kernel.root_dir%/Resources/assets/scss/bootstrap-flatly.scss"
"%kernel.root_dir%/Resources/assets/scss/font-awesome.scss"
"%kernel.root_dir%/Resources/assets/css/*.css"
"%kernel.root_dir%/Resources/assets/scss/main.scss"
%}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
#}
{% block flash_messages %}
{{ include('default/_flash_messages.html.twig') }}
{% endblock %}
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
{% endblock %}
{#
<title>{% block title %}{{ 'browser.title'|trans }}{% endblock %}</title>
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
</head>
<body id="{% block body_id %}{% endblock %}">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-globe"></i> <span class="caret"></span></a>
<ul class="dropdown-menu locales" role="menu">
{% for locale in locales() %}
<li {% if app.request.locale == locale.code %}class="active"{% endif %}><a href="{{ path(app.request.get('_route', 'homepage'), app.request.get('_route_params', [])|merge({ _locale: locale.code })) }}">{{ locale.name|capitalize }}</a></li>
{% endfor %}
</ul>
</li>
{% block header %}
<header>
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="{{ path('homepage') }}">
Timesheet
</a>
<button type="button" class="navbar-toggle"
data-toggle="collapse"
data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse">
{% block header_navigation_links %}
{{ knp_menu_render('main', {'template': 'menu/main_menu.html.twig'}) }}
{% endblock %}
<p>&copy; {{ 'now'|date('Y') }} - {{ 'kimai.footer'|trans }}</p>
<p>{{ 'kimai.license'|trans }}</p>
{#
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-globe"></i> <span class="caret"></span></a>
<ul class="dropdown-menu locales" role="menu">
{% for locale in locales() %}
<li {% if app.request.locale == locale.code %}class="active"{% endif %}><a href="{{ path(app.request.get('_route', 'blog_index'), app.request.get('_route_params', [])|merge({ _locale: locale.code })) }}">{{ locale.name|capitalize }}</a></li>
{% endfor %}
</ul>
</li>
#}
<!-- Page rendered on {{ 'now'|localizeddate('long', 'long', null, 'UTC') }} -->
</div>
</div>
</div>
</header>
{% endblock %}
<div class="container body-container">
{% block body %}
<div class="row">
<div id="main" class="col-sm-12">
{{ include('default/_flash_messages.html.twig') }}
{% block main %}{% endblock %}
</div>
</div>
{% endblock %}
</div>
{% block footer %}
<footer>
<div class="container">
<div class="row">
<div id="footer-copyright" class="col-md-6">
<p>&copy; {{ 'now'|date('Y') }} - {{ 'kimai.footer'|trans }}</p>
<p>{{ 'kimai.license'|trans }}</p>
</div>
<div id="footer-resources" class="col-md-6">
<p>
<a href="https://twitter.com/symfony"><i class="fa fa-twitter"></i></a>
<a href="https://www.facebook.com/SensioLabs"><i class="fa fa-facebook"></i></a>
<a href="https://symfony.com/blog/"><i class="fa fa-rss"></i></a>
</p>
</div>
</div>
</div>
</footer>
{% endblock %}
{% block javascripts %}
{# uncomment the following lines to combine and minimize JavaScript assets with Assetic
{% javascripts filter="?jsqueeze" output="js/app.js"
"%kernel.root_dir%/Resources/assets/js/jquery-2.1.4.js"
"%kernel.root_dir%/Resources/assets/js/moment.min.js"
"%kernel.root_dir%/Resources/assets/js/bootstrap-3.3.4.js"
"%kernel.root_dir%/Resources/assets/js/highlight.pack.js"
"%kernel.root_dir%/Resources/assets/js/bootstrap-datetimepicker.min.js"
"%kernel.root_dir%/Resources/assets/js/main.js" %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
#}
<script src="{{ asset('js/app.js') }}"></script>
{% endblock %}
{# it's not mandatory to set the timezone in localizeddate(). This is done to
avoid errors when the 'intl' PHP extension is not available and the application
is forced to use the limited "intl polyfill", which only supports UTC and GMT #}
<!-- Page rendered on {{ 'now'|localizeddate('long', 'long', null, 'UTC') }} -->
</body>
</html>
#}

View File

@@ -1,35 +0,0 @@
{#
By default, forms enable client-side validation. This means that you can't
test the server-side validation errors from the browser. To temporarily
disable this validation, add the 'novalidate' attribute:
{{ form_start(form, { method: ..., action: ..., attr: { novalidate: 'novalidate' } }) }}
#}
{{ form_start(form, { method: 'POST', action: path('comment_new', { 'postSlug': post.slug }) }) }}
{# instead of displaying form fields one by one, you can also display them
all with their default options and styles just by calling to this function:
{{ form_widget(form) }}
#}
<fieldset>
<legend>{{ 'title.add_comment'|trans }}</legend>
{# Render any global form error (e.g. when a constraint on a public getter method failed) #}
{{ form_errors(form) }}
<div class="form-group {% if not form.content.vars.valid %}has-error{% endif %}">
{{ form_label(form.content, 'label.content', { label_attr: { class: 'hidden' }}) }}
{# Render any errors for the "content" field (e.g. when a class property constraint failed) #}
{{ form_errors(form.content) }}
{{ form_widget(form.content, { attr: { rows: 10 } }) }}
</div>
<div class="form-group">
<button class="btn btn-primary pull-right" type="submit">{{ 'action.publish_comment'|trans }}</button>
</div>
</fieldset>
{{ form_end(form) }}

View File

@@ -1,19 +0,0 @@
{# Bootstrap modal, see http://getbootstrap.com/javascript/#modals #}
<div class="modal fade" id="confirmationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<h4>{{ 'delete_post_modal.title'|trans }}</h4>
<p>{{ 'delete_post_modal.body'|trans }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="btnNo" data-dismiss="modal">
{{ 'label.cancel'|trans }}
</button>
<button type="button" class="btn btn-danger" id="btnYes" data-dismiss="modal">
{{ 'label.delete_post'|trans }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,15 +0,0 @@
<div class="section about">
<div class="well well-lg">
<p>
{{ 'help.app_description'|trans|raw }}
</p>
<p>
{{ 'help.more_information'|trans|raw }}
</p>
</div>
</div>
{# it's not mandatory to set the timezone in localizeddate(). This is done to
avoid errors when the 'intl' PHP extension is not available and the application
is forced to use the limited "intl polyfill", which only supports UTC and GMT #}
<!-- Fragment rendered on {{ 'now'|localizeddate('long', 'long', null, 'UTC') }} -->

View File

@@ -1,11 +0,0 @@
{% extends 'base.html.twig' %}
{% block body_id 'comment_form_error' %}
{% block main %}
<h1 class="text-danger">{{ 'title.comment_error'|trans }}</h1>
<div class="well">
{{ include('blog/_comment_form.html.twig') }}
</div>
{% endblock %}

View File

@@ -1,27 +0,0 @@
{% extends 'base.html.twig' %}
{% block body_id 'blog_index' %}
{% block main %}
{% for post in posts %}
<article class="post">
<h2>
<a href="{{ path('blog_post', { slug: post.slug }) }}">
{{ post.title }}
</a>
</h2>
{{ post.summary|md2html }}
</article>
{% else %}
<div class="well">{{ 'post.no_posts_found'|trans }}</div>
{% endfor %}
<div class="navigation text-center">
{{ pagerfanta(posts, 'twitter_bootstrap3_translated', { routeName: 'blog_index_paginated' }) }}
</div>
{% endblock %}
{% block sidebar %}
{{ parent() }}
{% endblock %}

View File

@@ -1,64 +0,0 @@
{% extends 'base.html.twig' %}
{% block body_id 'blog_post_show' %}
{% block main %}
<h1>{{ post.title }}</h1>
{{ post.content|md2html }}
<div id="post-add-comment" class="well">
{# The 'IS_AUTHENTICATED_FULLY' role ensures that the user has entered
his/her credentials (login + password) during this session. If he/she
is automatically logged via the 'Remember Me' functionality, he/she won't
be able to add a comment.
See http://symfony.com/doc/current/cookbook/security/remember_me.html#forcing-the-user-to-re-authenticate-before-accessing-certain-resources
#}
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
{{ render(controller('AppBundle:Blog:commentForm', { 'id': post.id })) }}
{% else %}
<p>
<a class="btn btn-success" href="{{ path('security_login') }}">
<i class="fa fa-sign-in"></i> {{ 'action.sign_in'|trans }}
</a>
{{ 'post.to_publish_a_comment'|trans }}
</p>
{% endif %}
</div>
<h3>{{ 'post.num_comments'|transchoice(post.comments|length) }}</h3>
{% for comment in post.comments %}
<div class="row post-comment">
<h4 class="col-sm-3">
<strong>{{ comment.authorEmail }}</strong> {{ 'post.commented_on'|trans }}
{# it's not mandatory to set the timezone in localizeddate(). This is done to
avoid errors when the 'intl' PHP extension is not available and the application
is forced to use the limited "intl polyfill", which only supports UTC and GMT #}
<strong>{{ comment.publishedAt|localizeddate('medium', 'short', null, 'UTC') }}</strong>
</h4>
<div class="col-sm-9">
{{ comment.content|md2html }}
</div>
</div>
{% else %}
<div class="post-comment">
<p>{{ 'post.no_comments'|trans }}</p>
</div>
{% endfor %}
{% endblock %}
{% block sidebar %}
{% if app.user and post.isAuthor(app.user) %}
<div class="section">
<a class="btn btn-lg btn-block btn-success" href="{{ path('admin_post_edit', { id: post.id }) }}">
<i class="fa fa-edit"></i> {{ 'action.edit_post'|trans }}
</a>
</div>
{% endif %}
{# the parent() function includes the contents defined by the parent template
('base.html.twig') for this block ('sidebar'). This is a very convenient way
to share common contents in different templates #}
{{ parent() }}
{% endblock %}

View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{% block title %}{{ 'browser.title'|trans }}{% endblock %}</title>
{% block stylesheets %}
{# uncomment the following lines to compile SCSS assets with Assetic
{% stylesheets filter="scssphp" output="css/app.css"
"%kernel.root_dir%/Resources/assets/scss/bootstrap-flatly.scss"
"%kernel.root_dir%/Resources/assets/scss/font-awesome.scss"
"%kernel.root_dir%/Resources/assets/css/*.css"
"%kernel.root_dir%/Resources/assets/scss/main.scss"
%}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
#}
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
</head>
<body id="{% block body_id %}{% endblock %}">
{% block header %}
<header>
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="{{ path('homepage') }}">
{{ 'app.title'|trans }}
</a>
<button type="button" class="navbar-toggle"
data-toggle="collapse"
data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse">
{#
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-globe"></i> <span class="caret"></span></a>
<ul class="dropdown-menu locales" role="menu">
{% for locale in locales() %}
<li {% if app.request.locale == locale.code %}class="active"{% endif %}><a href="{{ path(app.request.get('_route', 'blog_index'), app.request.get('_route_params', [])|merge({ _locale: locale.code })) }}">{{ locale.name|capitalize }}</a></li>
{% endfor %}
</ul>
</li>
#}
</div>
</div>
</div>
</header>
{% endblock %}
<div class="container body-container">
{% block body %}
<div class="row">
<div id="main" class="col-sm-12">
{{ include('default/_flash_messages.html.twig') }}
{% block main %}{% endblock %}
</div>
</div>
{% endblock %}
</div>
{% block footer %}
<footer>
<div class="container">
<div class="row">
<div id="footer-copyright" class="col-md-6">
<p>&copy; {{ 'now'|date('Y') }} - {{ 'kimai.footer'|trans }}</p>
<p>{{ 'kimai.license'|trans }}</p>
</div>
<div id="footer-resources" class="col-md-6">
{#
<p>
<a href="https://twitter.com/symfony"><i class="fa fa-twitter"></i></a>
<a href="https://www.facebook.com/SensioLabs"><i class="fa fa-facebook"></i></a>
<a href="https://symfony.com/blog/"><i class="fa fa-rss"></i></a>
</p>
#}
</div>
</div>
</div>
</footer>
{% endblock %}
{% block javascripts %}
{# uncomment the following lines to combine and minimize JavaScript assets with Assetic
{% javascripts filter="?jsqueeze" output="js/app.js"
"%kernel.root_dir%/Resources/assets/js/jquery-2.1.4.js"
"%kernel.root_dir%/Resources/assets/js/moment.min.js"
"%kernel.root_dir%/Resources/assets/js/bootstrap-3.3.4.js"
"%kernel.root_dir%/Resources/assets/js/highlight.pack.js"
"%kernel.root_dir%/Resources/assets/js/bootstrap-datetimepicker.min.js"
"%kernel.root_dir%/Resources/assets/js/main.js" %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
#}
<script src="{{ asset('js/app.js') }}"></script>
{% endblock %}
{# it's not mandatory to set the timezone in localizeddate(). This is done to
avoid errors when the 'intl' PHP extension is not available and the application
is forced to use the limited "intl polyfill", which only supports UTC and GMT #}
<!-- Page rendered on {{ 'now'|localizeddate('long', 'long', null, 'UTC') }} -->
</body>
</html>

View File

@@ -0,0 +1,7 @@
{% extends 'base.html.twig' %}
{% block page_title %}Dashboard{% endblock %}
{% block page_subtitle %}Welcome!{% endblock %}
{% block main %}
{% endblock %}

View File

@@ -1 +0,0 @@
{% extends 'menu/main_menu.html.twig' %}

View File

@@ -1,18 +0,0 @@
{% extends 'knp_menu.html.twig' %}
{% block linkElement %}
<a href="{{ item.uri }}"{{ knp_menu.attributes(item.linkAttributes) }}>
{% if item.childrenAttributes.icon is defined %}
<i class="fa fa-{{ item.childrenAttributes.icon }} }}"></i>
{% endif %}
{{ block('label') }}
</a>
{% endblock %}
{% block label %}
{% if options.allow_safe_labels and item.getExtra('safe_label', false) %}
{{ item.label|raw }}
{% else %}
{{ item.label|trans }}
{% endif %}
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html.twig' %}
{% extends 'bootstrap.html.twig' %}
{% block body_id 'login' %}
@@ -40,6 +40,7 @@
{{ parent() }}
<script>
// FIXME remove me
$(document).ready(function() {
var usernameEl = $('#username');
var passwordEl = $('#password');

View File

@@ -57,6 +57,8 @@ twig:
form_themes:
- "bootstrap_3_layout.html.twig"
- "form/fields.html.twig"
globals:
admin_skin: skin-blue
# Assetic Configuration (used for managing web assets: CSS, JavaScript, Sass, etc.)
assetic:
@@ -86,3 +88,6 @@ swiftmailer:
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
#avanzu_admin_theme:
# use_assetic: false

View File

@@ -32,3 +32,8 @@ homepage:
_controller: FrameworkBundle:Redirect:redirect
route: timesheet
permanent: true
avanzu_admin_profile:
path: /profile/{userid}/
avanzu_admin_logout:
path: /logout

View File

@@ -1,6 +1,4 @@
services:
# First we define some basic services to make these utilities available in
# the entire application
slugger:
class: AppBundle\Utils\Slugger
@@ -21,19 +19,21 @@ services:
tags:
- { name: twig.extension }
# a route listener, that injects the locale through a URL directory
app.redirect_to_preferred_locale_listener:
class: AppBundle\EventListener\RedirectToPreferredLocaleListener
arguments: ['@router', '%app_locales%', '%locale%']
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
# event-listener to populate the navigation
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
arguments: ["@knp_menu.factory", "@event_dispatcher", "@security.authorization_checker"]
class: AppBundle\EventListener\MenuBuilder
arguments: ["@event_dispatcher", "@security.authorization_checker"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main }
- { name: knp_menu.menu_builder, method: createAdminMenu, alias: admin }
- { name: kernel.event_listener, event:theme.sidebar_setup_menu, method:onSetupNavbar }
# service that prefixes every database table
app.tableprefix_subscriber:
class: AppBundle\Doctrine\TablePrefixSubscriber
arguments: ["%database_prefix%"]

View File

@@ -31,7 +31,7 @@
"symfony/symfony": "^3.1",
"twig/extensions": "^1.3",
"white-october/pagerfanta-bundle": "^1.0",
"knplabs/knp-menu-bundle": "^2.0"
"avanzu/admin-theme-bundle": "^1.3"
},
"require-dev": {
"sensio/generator-bundle": "~3.0",

172
composer.lock generated
View File

@@ -4,9 +4,54 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "5047e9bd8560365d5765f09e782b4ced",
"content-hash": "7a606e9098e49dd364fa6e997f56e699",
"hash": "8c9fa238b0d9190a97312f91782fb7a2",
"content-hash": "5c5c4734f4e8e74afdfd434661f75a7d",
"packages": [
{
"name": "avanzu/admin-theme-bundle",
"version": "1.3.4",
"source": {
"type": "git",
"url": "https://github.com/avanzu/AdminThemeBundle.git",
"reference": "24a1ac93ace744b5e8cffe6681009a4cc8f3a133"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/avanzu/AdminThemeBundle/zipball/24a1ac93ace744b5e8cffe6681009a4cc8f3a133",
"reference": "24a1ac93ace744b5e8cffe6681009a4cc8f3a133",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/assetic-bundle": ">=2.3",
"symfony/event-dispatcher": ">=2.3",
"symfony/http-foundation": ">=2.3",
"symfony/http-kernel": ">=2.3",
"symfony/twig-bundle": ">=2.3"
},
"require-dev": {
"hamcrest/hamcrest-php": "dev-master",
"mockery/mockery": "0.9.*@dev"
},
"type": "library",
"autoload": {
"psr-4": {
"Avanzu\\AdminThemeBundle\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marc Bach",
"email": "mail@avanzu.de"
}
],
"description": "Admin Theme based on the AdminLTE Template for easy integration into symfony",
"time": "2015-12-29 23:17:55"
},
{
"name": "doctrine/annotations",
"version": "v1.2.7",
@@ -1073,129 +1118,6 @@
],
"time": "2014-01-12 16:20:24"
},
{
"name": "knplabs/knp-menu",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/KnpMenu.git",
"reference": "964b5b3ca7fd23019147991f4f75f361d061eb20"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/KnpMenu/zipball/964b5b3ca7fd23019147991f4f75f361d061eb20",
"reference": "964b5b3ca7fd23019147991f4f75f361d061eb20",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"pimple/pimple": "~1.0",
"silex/silex": "~1.0",
"symfony/phpunit-bridge": "~2.7|~3.0",
"symfony/routing": "~2.3|~3.0",
"twig/twig": "~1.16|~2.0"
},
"suggest": {
"pimple/pimple": "for the built-in implementations of the menu provider and renderer provider",
"silex/silex": "for the integration with your silex application",
"twig/twig": "for the TwigRenderer and the integration with your templates"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
},
"autoload": {
"psr-4": {
"Knp\\Menu\\": "src/Knp/Menu"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christophe Coevoet",
"email": "stof@notk.org"
},
{
"name": "KnpLabs",
"homepage": "http://knplabs.com"
},
{
"name": "Symfony Community",
"homepage": "https://github.com/KnpLabs/KnpMenu/contributors"
}
],
"description": "An object oriented menu library",
"homepage": "http://knplabs.com",
"keywords": [
"menu",
"tree"
],
"time": "2016-09-22 07:36:19"
},
{
"name": "knplabs/knp-menu-bundle",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/KnpMenuBundle.git",
"reference": "0e4af7209dc03e39c51ec70b68ab2ba3177c25de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/KnpMenuBundle/zipball/0e4af7209dc03e39c51ec70b68ab2ba3177c25de",
"reference": "0e4af7209dc03e39c51ec70b68ab2ba3177c25de",
"shasum": ""
},
"require": {
"knplabs/knp-menu": "~2.2",
"symfony/framework-bundle": "~2.3|~3.0"
},
"require-dev": {
"symfony/expression-language": "~2.4|~3.0",
"symfony/phpunit-bridge": "~2.7|~3.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"psr-4": {
"Knp\\Bundle\\MenuBundle\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christophe Coevoet",
"email": "stof@notk.org"
},
{
"name": "Knplabs",
"homepage": "http://knplabs.com"
},
{
"name": "Symfony Community",
"homepage": "https://github.com/KnpLabs/KnpMenuBundle/contributors"
}
],
"description": "This bundle provides an integration of the KnpMenu library",
"keywords": [
"menu"
],
"time": "2016-09-22 12:24:40"
},
{
"name": "kriswallsmith/assetic",
"version": "v1.3.2",

View File

@@ -1,280 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Doctrine\Common\Persistence\ObjectManager;
use AppBundle\Entity\User;
/**
* A command console that creates users and stores them in the database.
* To use this command, open a terminal window, enter into your project
* directory and execute the following:
*
* $ php bin/console app:add-user
*
* To output detailed information, increase the command verbosity:
*
* $ php bin/console app:add-user -vv
*
* See http://symfony.com/doc/current/cookbook/console/console_command.html
*
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class AddUserCommand extends ContainerAwareCommand
{
const MAX_ATTEMPTS = 5;
/**
* @var ObjectManager
*/
private $entityManager;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
// a good practice is to use the 'app:' prefix to group all your custom application commands
->setName('app:add-user')
->setDescription('Creates users and stores them in the database')
->setHelp($this->getCommandHelp())
// commands can optionally define arguments and/or options (mandatory and optional)
// see http://symfony.com/doc/current/components/console/console_arguments.html
->addArgument('username', InputArgument::OPTIONAL, 'The username of the new user')
->addArgument('password', InputArgument::OPTIONAL, 'The plain password of the new user')
->addArgument('email', InputArgument::OPTIONAL, 'The email of the new user')
->addOption('is-admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator')
;
}
/**
* This method is executed before the interact() and the execute() methods.
* It's main purpose is to initialize the variables used in the rest of the
* command methods.
*
* Beware that the input options and arguments are validated after executing
* the interact() method, so you can't blindly trust their values in this method.
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->entityManager = $this->getContainer()->get('doctrine')->getManager();
}
/**
* This method is executed after initialize() and before execute(). Its purpose
* is to check if some of the options/arguments are missing and interactively
* ask the user for those values.
*
* This method is completely optional. If you are developing an internal console
* command, you probably should not implement this method because it requires
* quite a lot of work. However, if the command is meant to be used by external
* users, this method is a nice way to fall back and prevent errors.
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
if (null !== $input->getArgument('username') && null !== $input->getArgument('password') && null !== $input->getArgument('email')) {
return;
}
// multi-line messages can be displayed this way...
$output->writeln('');
$output->writeln('Add User Command Interactive Wizard');
$output->writeln('-----------------------------------');
// ...but you can also pass an array of strings to the writeln() method
$output->writeln([
'',
'If you prefer to not use this interactive wizard, provide the',
'arguments required by this command as follows:',
'',
' $ php bin/console app:add-user username password email@example.com',
'',
]);
$output->writeln([
'',
'Now we\'ll ask you for the value of all the missing command arguments.',
'',
]);
// See http://symfony.com/doc/current/components/console/helpers/questionhelper.html
$console = $this->getHelper('question');
// Ask for the username if it's not defined
$username = $input->getArgument('username');
if (null === $username) {
$question = new Question(' > <info>Username</info>: ');
$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new \RuntimeException('The username cannot be empty');
}
return $answer;
});
$question->setMaxAttempts(self::MAX_ATTEMPTS);
$username = $console->ask($input, $output, $question);
$input->setArgument('username', $username);
} else {
$output->writeln(' > <info>Username</info>: '.$username);
}
// Ask for the password if it's not defined
$password = $input->getArgument('password');
if (null === $password) {
$question = new Question(' > <info>Password</info> (your type will be hidden): ');
$question->setValidator([$this, 'passwordValidator']);
$question->setHidden(true);
$question->setMaxAttempts(self::MAX_ATTEMPTS);
$password = $console->ask($input, $output, $question);
$input->setArgument('password', $password);
} else {
$output->writeln(' > <info>Password</info>: '.str_repeat('*', strlen($password)));
}
// Ask for the email if it's not defined
$email = $input->getArgument('email');
if (null === $email) {
$question = new Question(' > <info>Email</info>: ');
$question->setValidator([$this, 'emailValidator']);
$question->setMaxAttempts(self::MAX_ATTEMPTS);
$email = $console->ask($input, $output, $question);
$input->setArgument('email', $email);
} else {
$output->writeln(' > <info>Email</info>: '.$email);
}
}
/**
* This method is executed after interact() and initialize(). It usually
* contains the logic to execute to complete this command task.
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$startTime = microtime(true);
$username = $input->getArgument('username');
$plainPassword = $input->getArgument('password');
$email = $input->getArgument('email');
$isAdmin = $input->getOption('is-admin');
// first check if a user with the same username already exists
$existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $username]);
if (null !== $existingUser) {
throw new \RuntimeException(sprintf('There is already a user registered with the "%s" username.', $username));
}
// create the user and encode its password
$user = new User();
$user->setUsername($username);
$user->setEmail($email);
$user->setRoles([$isAdmin ? 'ROLE_ADMIN' : 'ROLE_USER']);
// See http://symfony.com/doc/current/book/security.html#security-encoding-password
$encoder = $this->getContainer()->get('security.password_encoder');
$encodedPassword = $encoder->encodePassword($user, $plainPassword);
$user->setPassword($encodedPassword);
$this->entityManager->persist($user);
$this->entityManager->flush();
$output->writeln('');
$output->writeln(sprintf('[OK] %s was successfully created: %s (%s)', $isAdmin ? 'Administrator user' : 'User', $user->getUsername(), $user->getEmail()));
if ($output->isVerbose()) {
$finishTime = microtime(true);
$elapsedTime = $finishTime - $startTime;
$output->writeln(sprintf('[INFO] New user database id: %d / Elapsed time: %.2f ms', $user->getId(), $elapsedTime*1000));
}
}
/**
* This internal method should be private, but it's declared as public to
* maintain PHP 5.3 compatibility when using it in a callback.
*
* @internal
*/
public function passwordValidator($plainPassword)
{
if (empty($plainPassword)) {
throw new \Exception('The password can not be empty');
}
if (strlen(trim($plainPassword)) < 6) {
throw new \Exception('The password must be at least 6 characters long');
}
return $plainPassword;
}
/**
* This internal method should be private, but it's declared as public to
* maintain PHP 5.3 compatibility when using it in a callback.
*
* @internal
*/
public function emailValidator($email)
{
if (empty($email)) {
throw new \Exception('The email can not be empty');
}
if (false === strpos($email, '@')) {
throw new \Exception('The email should look like a real email');
}
return $email;
}
/**
* The command help is usually included in the configure() method, but when
* it's too long, it's better to define a separate method to maintain the
* code readability.
*/
private function getCommandHelp()
{
return <<<HELP
The <info>%command.name%</info> command creates new users and saves them in the database:
<info>php %command.full_name%</info> <comment>username password email</comment>
By default the command creates regular users. To create administrator users,
add the <comment>--is-admin</comment> option:
<info>php %command.full_name%</info> username password email <comment>--is-admin</comment>
If you omit any of the three required arguments, the command will ask you to
provide the missing values:
# command will ask you for the email
<info>php %command.full_name%</info> <comment>username password</comment>
# command will ask you for the email and password
<info>php %command.full_name%</info> <comment>username</comment>
# command will ask you for all arguments
<info>php %command.full_name%</info>
HELP;
}
}

View File

@@ -1,149 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Command;
use AppBundle\Entity\User;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Doctrine\Common\Persistence\ObjectManager;
/**
* A command console that deletes users from the database.
* To use this command, open a terminal window, enter into your project
* directory and execute the following:
*
* $ php bin/console app:delete-user
*
* Check out the code of the src/AppBundle/Command/AddUserCommand.php file for
* the full explanation about Symfony commands.
* See http://symfony.com/doc/current/cookbook/console/console_command.html
*
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
*/
class DeleteUserCommand extends ContainerAwareCommand
{
const MAX_ATTEMPTS = 5;
/**
* @var ObjectManager
*/
private $entityManager;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('app:delete-user')
->setDescription('Deletes users from the database')
->addArgument('username', InputArgument::REQUIRED, 'The username of an existing user')
->setHelp(<<<HELP
The <info>%command.name%</info> command deletes users from the database:
<info>php %command.full_name%</info> <comment>username</comment>
If you omit the argument, the command will ask you to
provide the missing value:
<info>php %command.full_name%</info>
HELP
);
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->entityManager = $this->getContainer()->get('doctrine')->getManager();
}
protected function interact(InputInterface $input, OutputInterface $output)
{
if (null !== $input->getArgument('username')) {
return;
}
$output->writeln('');
$output->writeln('Delete User Command Interactive Wizard');
$output->writeln('-----------------------------------');
$output->writeln([
'',
'If you prefer to not use this interactive wizard, provide the',
'arguments required by this command as follows:',
'',
' $ php bin/console app:delete-user username',
'',
]);
$output->writeln([
'',
'Now we\'ll ask you for the value of all the missing command arguments.',
'',
]);
$helper = $this->getHelper('question');
$question = new Question(' > <info>Username</info>: ');
$question->setValidator([$this, 'usernameValidator']);
$question->setMaxAttempts(self::MAX_ATTEMPTS);
$username = $helper->ask($input, $output, $question);
$input->setArgument('username', $username);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$username = $input->getArgument('username');
$this->usernameValidator($username);
$repository = $this->entityManager->getRepository(User::class);
/** @var User $user */
$user = $repository->findOneByUsername($username);
if (null === $user) {
throw new \RuntimeException(sprintf('User with username "%s" not found.', $username));
}
// After an entity has been removed its in-memory state is the same
// as before the removal, except for generated identifiers.
// See http://docs.doctrine-project.org/en/latest/reference/working-with-objects.html#removing-entities
$userId = $user->getId();
$this->entityManager->remove($user);
$this->entityManager->flush();
$output->writeln('');
$output->writeln(sprintf('[OK] User "%s" (ID: %d, email: %s) was successfully deleted.', $user->getUsername(), $userId, $user->getEmail()));
}
/**
* This internal method should be private, but it's declared public to
* maintain PHP 5.3 compatibility when using it in a callback.
*
* @internal
*/
public function usernameValidator($username)
{
if (empty($username)) {
throw new \Exception('The username can not be empty.');
}
if (1 !== preg_match('/^[a-z_]+$/', $username)) {
throw new \Exception('The username must contain only lowercase latin characters and underscores.');
}
return $username;
}
}

View File

@@ -1,143 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Command;
use AppBundle\Entity\User;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
/**
* A command console that lists all the existing users. To use this command, open
* a terminal window, enter into your project directory and execute the following:
*
* $ php bin/console app:list-users
*
* See http://symfony.com/doc/current/cookbook/console/console_command.html
*
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class ListUsersCommand extends ContainerAwareCommand
{
/**
* @var ObjectManager
*/
private $entityManager;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
// a good practice is to use the 'app:' prefix to group all your custom application commands
->setName('app:list-users')
->setDescription('Lists all the existing users')
->setHelp(<<<HELP
The <info>%command.name%</info> command lists all the users registered in the application:
<info>php %command.full_name%</info>
By default the command only displays the 50 most recent users. Set the number of
results to display with the <comment>--max-results</comment> option:
<info>php %command.full_name%</info> <comment>--max-results=2000</comment>
In addition to displaying the user list, you can also send this information to
the email address specified in the <comment>--send-to</comment> option:
<info>php %command.full_name%</info> <comment>--send-to=fabien@symfony.com</comment>
HELP
)
// commands can optionally define arguments and/or options (mandatory and optional)
// see http://symfony.com/doc/current/components/console/console_arguments.html
->addOption('max-results', null, InputOption::VALUE_OPTIONAL, 'Limits the number of users listed', 50)
->addOption('send-to', null, InputOption::VALUE_OPTIONAL, 'If set, the result is sent to the given email address')
;
}
/**
* This method is executed before the the execute() method. It's main purpose
* is to initialize the variables used in the rest of the command methods.
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->entityManager = $this->getContainer()->get('doctrine')->getManager();
}
/**
* This method is executed after initialize(). It usually contains the logic
* to execute to complete this command task.
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$maxResults = $input->getOption('max-results');
// Use ->findBy() instead of ->findAll() to allow result sorting and limiting
$users = $this->entityManager->getRepository(User::class)->findBy([], ['id' => 'DESC'], $maxResults);
// Doctrine query returns an array of objects and we need an array of plain arrays
$usersAsPlainArrays = array_map(function (User $user) {
return [$user->getId(), $user->getUsername(), $user->getEmail(), implode(', ', $user->getRoles())];
}, $users);
// In your console commands you should always use the regular output type,
// which outputs contents directly in the console window. However, this
// particular command uses the BufferedOutput type instead.
// The reason is that the table displaying the list of users can be sent
// via email if the '--send-to' option is provided. Instead of complicating
// things, the BufferedOutput allows to get the command output and store
// it in a variable before displaying it.
$bufferedOutput = new BufferedOutput();
$table = new Table($bufferedOutput);
$table
->setHeaders(['ID', 'Username', 'Email', 'Roles'])
->setRows($usersAsPlainArrays)
;
$table->render();
// instead of displaying the table of users, store it in a variable
$tableContents = $bufferedOutput->fetch();
if (null !== $email = $input->getOption('send-to')) {
$this->sendReport($tableContents, $email);
}
$output->writeln($tableContents);
}
/**
* Sends the given $contents to the $recipient email address.
*
* @param string $contents
* @param string $recipient
*/
private function sendReport($contents, $recipient)
{
// See http://symfony.com/doc/current/cookbook/email/email.html
$mailer = $this->getContainer()->get('mailer');
$message = $mailer->createMessage()
->setSubject(sprintf('app:list-users report (%s)', date('Y-m-d H:i:s')))
->setFrom($this->getContainer()->getParameter('app.notifications.email_sender'))
->setTo($recipient)
->setBody($contents, 'text/plain')
;
$mailer->send($message);
}
}

View File

@@ -1,222 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Controller\Admin;
use AppBundle\Entity\Post;
use AppBundle\Form\PostType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* FIXME CAN BE REMOVED
*
* Controller used to manage blog contents in the backend.
*
* Please note that the application backend is developed manually for learning
* purposes. However, in your real Symfony application you should use any of the
* existing bundles that let you generate ready-to-use backends without effort.
* See http://knpbundles.com/keyword/admin
*
* @Route("/admin/post")
* @Security("has_role('ROLE_ADMIN')")
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class BlogController extends Controller
{
/**
* Lists all Post entities.
*
* This controller responds to two different routes with the same URL:
* * 'admin_post_index' is the route with a name that follows the same
* structure as the rest of the controllers of this class.
* * 'admin_index' is a nice shortcut to the backend homepage. This allows
* to create simpler links in the templates. Moreover, in the future we
* could move this annotation to any other controller while maintaining
* the route name and therefore, without breaking any existing link.
*
* @Route("/", name="admin_index")
* @Route("/", name="admin_post_index")
* @Method("GET")
*/
public function indexAction()
{
$entityManager = $this->getDoctrine()->getManager();
$posts = $entityManager->getRepository(Post::class)->findAll();
return $this->render('admin/blog/index.html.twig', ['posts' => $posts]);
}
/**
* Creates a new Post entity.
*
* @Route("/new", name="admin_post_new")
* @Method({"GET", "POST"})
*
* NOTE: the Method annotation is optional, but it's a recommended practice
* to constraint the HTTP methods each controller responds to (by default
* it responds to all methods).
*/
public function newAction(Request $request)
{
$post = new Post();
$post->setAuthorEmail($this->getUser()->getEmail());
// See http://symfony.com/doc/current/book/forms.html#submitting-forms-with-multiple-buttons
$form = $this->createForm(PostType::class, $post)
->add('saveAndCreateNew', SubmitType::class);
$form->handleRequest($request);
// the isSubmitted() method is completely optional because the other
// isValid() method already checks whether the form is submitted.
// However, we explicitly add it to improve code readability.
// See http://symfony.com/doc/current/best_practices/forms.html#handling-form-submits
if ($form->isSubmitted() && $form->isValid()) {
$post->setSlug($this->get('slugger')->slugify($post->getTitle()));
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($post);
$entityManager->flush();
// Flash messages are used to notify the user about the result of the
// actions. They are deleted automatically from the session as soon
// as they are accessed.
// See http://symfony.com/doc/current/book/controller.html#flash-messages
$this->addFlash('success', 'post.created_successfully');
if ($form->get('saveAndCreateNew')->isClicked()) {
return $this->redirectToRoute('admin_post_new');
}
return $this->redirectToRoute('admin_post_index');
}
return $this->render('admin/blog/new.html.twig', [
'post' => $post,
'form' => $form->createView(),
]);
}
/**
* Finds and displays a Post entity.
*
* @Route("/{id}", requirements={"id": "\d+"}, name="admin_post_show")
* @Method("GET")
*/
public function showAction(Post $post)
{
// This security check can also be performed:
// 1. Using an annotation: @Security("post.isAuthor(user)")
// 2. Using a "voter" (see http://symfony.com/doc/current/cookbook/security/voters_data_permission.html)
if (null === $this->getUser() || !$post->isAuthor($this->getUser())) {
throw $this->createAccessDeniedException('Posts can only be shown to their authors.');
}
$deleteForm = $this->createDeleteForm($post);
return $this->render('admin/blog/show.html.twig', [
'post' => $post,
'delete_form' => $deleteForm->createView(),
]);
}
/**
* Displays a form to edit an existing Post entity.
*
* @Route("/{id}/edit", requirements={"id": "\d+"}, name="admin_post_edit")
* @Method({"GET", "POST"})
*/
public function editAction(Post $post, Request $request)
{
if (null === $this->getUser() || !$post->isAuthor($this->getUser())) {
throw $this->createAccessDeniedException('Posts can only be edited by their authors.');
}
$entityManager = $this->getDoctrine()->getManager();
$editForm = $this->createForm(PostType::class, $post);
$deleteForm = $this->createDeleteForm($post);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$post->setSlug($this->get('slugger')->slugify($post->getTitle()));
$entityManager->flush();
$this->addFlash('success', 'post.updated_successfully');
return $this->redirectToRoute('admin_post_edit', ['id' => $post->getId()]);
}
return $this->render('admin/blog/edit.html.twig', [
'post' => $post,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
]);
}
/**
* Deletes a Post entity.
*
* @Route("/{id}", name="admin_post_delete")
* @Method("DELETE")
* @Security("post.isAuthor(user)")
*
* The Security annotation value is an expression (if it evaluates to false,
* the authorization mechanism will prevent the user accessing this resource).
* The isAuthor() method is defined in the AppBundle\Entity\Post entity.
*/
public function deleteAction(Request $request, Post $post)
{
$form = $this->createDeleteForm($post);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($post);
$entityManager->flush();
$this->addFlash('success', 'post.deleted_successfully');
}
return $this->redirectToRoute('admin_post_index');
}
/**
* Creates a form to delete a Post entity by id.
*
* This is necessary because browsers don't support HTTP methods different
* from GET and POST. Since the controller that removes the blog posts expects
* a DELETE method, the trick is to create a simple form that *fakes* the
* HTTP DELETE method.
* See http://symfony.com/doc/current/cookbook/routing/method_parameters.html.
*
* @param Post $post The post object
*
* @return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm(Post $post)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('admin_post_delete', ['id' => $post->getId()]))
->setMethod('DELETE')
->getForm()
;
}
}

View File

@@ -1,132 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Controller;
use AppBundle\Entity\Comment;
use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Form\CommentType;
/**
* FIXME CAN BE REMOVED
*
* Controller used to manage blog contents in the public part of the site.
*
* @Route("/blog")
* @Security("has_role('ROLE_USER')")
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class BlogController extends Controller
{
/**
* @Route("/", defaults={"page": 1}, name="blog_index")
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="blog_index_paginated")
* @Method("GET")
* @Cache(smaxage="10")
*/
public function indexAction($page)
{
$posts = $this->getDoctrine()->getRepository(Post::class)->findLatest($page);
return $this->render('blog/index.html.twig', ['posts' => $posts]);
}
/**
* @Route("/posts/{slug}", name="blog_post")
* @Method("GET")
*
* NOTE: The $post controller argument is automatically injected by Symfony
* after performing a database query looking for a Post with the 'slug'
* value given in the route.
* See http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
*/
public function postShowAction(Post $post)
{
// Symfony provides a function called 'dump()' which is an improved version
// of the 'var_dump()' function. It's useful to quickly debug the contents
// of any variable, but it's not available in the 'prod' environment to
// prevent any leak of sensitive information.
// This function can be used both in PHP files and Twig templates. The only
// requirement is to have enabled the DebugBundle.
if ('dev' === $this->getParameter('kernel.environment')) {
dump($post, $this->get('security.token_storage')->getToken()->getUser(), new \DateTime());
}
return $this->render('blog/post_show.html.twig', ['post' => $post]);
}
/**
* @Route("/comment/{postSlug}/new", name="comment_new")
* @Method("POST")
* @Security("is_granted('IS_AUTHENTICATED_FULLY')")
* @ParamConverter("post", options={"mapping": {"postSlug": "slug"}})
*
* NOTE: The ParamConverter mapping is required because the route parameter
* (postSlug) doesn't match any of the Doctrine entity properties (slug).
* See http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html#doctrine-converter
*/
public function commentNewAction(Request $request, Post $post)
{
$form = $this->createForm(CommentType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var Comment $comment */
$comment = $form->getData();
$comment->setAuthorEmail($this->getUser()->getEmail());
$comment->setPost($post);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($comment);
$entityManager->flush();
return $this->redirectToRoute('blog_post', ['slug' => $post->getSlug()]);
}
return $this->render('blog/comment_form_error.html.twig', [
'post' => $post,
'form' => $form->createView(),
]);
}
/**
* This controller is called directly via the render() function in the
* blog/post_show.html.twig template. That's why it's not needed to define
* a route name for it.
*
* The "id" of the Post is passed in and then turned into a Post object
* automatically by the ParamConverter.
*
* @param Post $post
*
* @return Response
*/
public function commentFormAction(Post $post)
{
$form = $this->createForm(CommentType::class);
return $this->render('blog/_comment_form.html.twig', [
'post' => $post,
'form' => $form->createView(),
]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Controller;
use TimesheetBundle\Entity\Timesheet;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* Dashboard controller for the admin area.
*
* @Route("/dashboard")
* @Security("has_role('ROLE_USER')")
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class DashboardController extends Controller
{
/**
* @Route("/", defaults={}, name="dashboard")
* @Method("GET")
*/
public function indexAction()
{
return $this->render('dashboard/index.html.twig', []);
}
}

View File

@@ -1,9 +1,9 @@
<?php
/*
* This file is part of the Symfony package.
* This file is part of the Kimai package.
*
* (c) Fabien Potencier <fabien@symfony.com>
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -12,23 +12,15 @@
namespace AppBundle\DataFixtures\ORM;
use AppBundle\Entity\User;
use AppBundle\Entity\Post;
use AppBundle\Entity\Comment;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the sample data to load in the database when running the unit and
* functional tests. Execute this command to load the data:
* Sample data to load in the database when running tests or for development
*
* $ php bin/console doctrine:fixtures:load
*
* See http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class LoadFixtures implements FixtureInterface, ContainerAwareInterface
{
@@ -40,9 +32,8 @@ class LoadFixtures implements FixtureInterface, ContainerAwareInterface
*/
public function load(ObjectManager $manager)
{
// FIXME CAN BE REMOVED
$this->loadUsers($manager);
$this->loadPosts($manager);
//$this->loadPosts($manager);
}
private function loadUsers(ObjectManager $manager)
@@ -84,8 +75,19 @@ class LoadFixtures implements FixtureInterface, ContainerAwareInterface
$manager->flush();
}
/**
* FIXME CAN BE REMOVED
*
* @param ObjectManager $manager
*/
private function loadPosts(ObjectManager $manager)
{
$authors = [
'anna_admin@example.com',
'tony_teamlead@example.com',
'clara_customer@example.com',
'john_user@example.com'
];
foreach (range(1, 30) as $i) {
$post = new Post();
@@ -93,13 +95,13 @@ class LoadFixtures implements FixtureInterface, ContainerAwareInterface
$post->setSummary($this->getRandomPostSummary());
$post->setSlug($this->container->get('slugger')->slugify($post->getTitle()));
$post->setContent($this->getPostContent());
$post->setAuthorEmail(array_rand(['anna_admin@example.com', 'tony_teamlead@example.com'], 1));
$post->setAuthorEmail($authors[array_rand($authors, 1)]);
$post->setPublishedAt(new \DateTime('now - '.$i.'days'));
foreach (range(1, 5) as $j) {
$comment = new Comment();
$comment->setAuthorEmail(array_rand(['clara_customer@example.com', 'john_user@example.com'], 1));
$comment->setAuthorEmail($authors[array_rand($authors, 1)]);
$comment->setPublishedAt(new \DateTime('now + '.($i + $j).'seconds'));
$comment->setContent($this->getRandomCommentContent());
$comment->setPost($post);

View File

@@ -1,126 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* FIXME CAN BE REMOVED
*
* @ORM\Entity
* @ORM\Table(name="demo_comment")
*
* Defines the properties of the Comment entity to represent the blog comments.
* See http://symfony.com/doc/current/book/doctrine.html#creating-an-entity-class
*
* Tip: if you have an existing database, you can generate these entity class automatically.
* See http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class Comment
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="Post", inversedBy="comments")
* @ORM\JoinColumn(nullable=false)
*/
private $post;
/**
* @ORM\Column(type="text")
* @Assert\NotBlank(message="comment.blank")
* @Assert\Length(
* min = "5",
* minMessage = "comment.too_short",
* max = "10000",
* maxMessage = "comment.too_long"
* )
*/
private $content;
/**
* @ORM\Column(type="string")
* @Assert\Email()
*/
private $authorEmail;
/**
* @ORM\Column(type="datetime")
* @Assert\DateTime()
*/
private $publishedAt;
public function __construct()
{
$this->publishedAt = new \DateTime();
}
/**
* @Assert\IsTrue(message = "comment.is_spam")
*/
public function isLegitComment()
{
$containsInvalidCharacters = false !== strpos($this->content, '@');
return !$containsInvalidCharacters;
}
public function getId()
{
return $this->id;
}
public function getContent()
{
return $this->content;
}
public function setContent($content)
{
$this->content = $content;
}
public function getAuthorEmail()
{
return $this->authorEmail;
}
public function setAuthorEmail($authorEmail)
{
$this->authorEmail = $authorEmail;
}
public function getPublishedAt()
{
return $this->publishedAt;
}
public function setPublishedAt(\DateTime $publishedAt)
{
$this->publishedAt = $publishedAt;
}
public function getPost()
{
return $this->post;
}
public function setPost(Post $post)
{
$this->post = $post;
}
}

View File

@@ -5,19 +5,13 @@ namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiConfiguration
* Configuration
*
* @ORM\Table(name="configuration")
* @ORM\Entity
*/
class KimaiConfiguration
class Configuration
{
/**
* @var string
*
* @ORM\Column(name="value", type="string", length=255, nullable=false)
*/
private $value;
/**
* @var string
@@ -28,14 +22,19 @@ class KimaiConfiguration
*/
private $option;
/**
* @var string
*
* @ORM\Column(name="value", type="string", length=255, nullable=false)
*/
private $value;
/**
* Set value
*
* @param string $value
*
* @return KimaiConfiguration
* @return Configuration
*/
public function setValue($value)
{

View File

@@ -1,345 +0,0 @@
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiExpenses
*
* @ORM\Table(name="expenses", indexes={@ORM\Index(name="userID", columns={"userID"}), @ORM\Index(name="projectID", columns={"projectID"})})
* @ORM\Entity
*/
class KimaiExpenses
{
/**
* @var integer
*
* @ORM\Column(name="timestamp", type="integer", nullable=false)
*/
private $timestamp = '0';
/**
* @var integer
*
* @ORM\Column(name="userID", type="integer", nullable=false)
*/
private $userid;
/**
* @var integer
*
* @ORM\Column(name="projectID", type="integer", nullable=false)
*/
private $projectid;
/**
* @var string
*
* @ORM\Column(name="designation", type="text", length=65535, nullable=false)
*/
private $designation;
/**
* @var string
*
* @ORM\Column(name="comment", type="text", length=65535, nullable=true)
*/
private $comment;
/**
* @var boolean
*
* @ORM\Column(name="commentType", type="boolean", nullable=false)
*/
private $commenttype = '0';
/**
* @var boolean
*
* @ORM\Column(name="refundable", type="boolean", nullable=false)
*/
private $refundable = '0';
/**
* @var boolean
*
* @ORM\Column(name="cleared", type="boolean", nullable=false)
*/
private $cleared = '0';
/**
* @var string
*
* @ORM\Column(name="multiplier", type="decimal", precision=10, scale=2, nullable=false)
*/
private $multiplier = '1.00';
/**
* @var string
*
* @ORM\Column(name="value", type="decimal", precision=10, scale=2, nullable=false)
*/
private $value = '0.00';
/**
* @var integer
*
* @ORM\Column(name="expenseID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $expenseid;
/**
* Set timestamp
*
* @param integer $timestamp
*
* @return KimaiExpenses
*/
public function setTimestamp($timestamp)
{
$this->timestamp = $timestamp;
return $this;
}
/**
* Get timestamp
*
* @return integer
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* Set userid
*
* @param integer $userid
*
* @return KimaiExpenses
*/
public function setUserid($userid)
{
$this->userid = $userid;
return $this;
}
/**
* Get userid
*
* @return integer
*/
public function getUserid()
{
return $this->userid;
}
/**
* Set projectid
*
* @param integer $projectid
*
* @return KimaiExpenses
*/
public function setProjectid($projectid)
{
$this->projectid = $projectid;
return $this;
}
/**
* Get projectid
*
* @return integer
*/
public function getProjectid()
{
return $this->projectid;
}
/**
* Set designation
*
* @param string $designation
*
* @return KimaiExpenses
*/
public function setDesignation($designation)
{
$this->designation = $designation;
return $this;
}
/**
* Get designation
*
* @return string
*/
public function getDesignation()
{
return $this->designation;
}
/**
* Set comment
*
* @param string $comment
*
* @return KimaiExpenses
*/
public function setComment($comment)
{
$this->comment = $comment;
return $this;
}
/**
* Get comment
*
* @return string
*/
public function getComment()
{
return $this->comment;
}
/**
* Set commenttype
*
* @param boolean $commenttype
*
* @return KimaiExpenses
*/
public function setCommenttype($commenttype)
{
$this->commenttype = $commenttype;
return $this;
}
/**
* Get commenttype
*
* @return boolean
*/
public function getCommenttype()
{
return $this->commenttype;
}
/**
* Set refundable
*
* @param boolean $refundable
*
* @return KimaiExpenses
*/
public function setRefundable($refundable)
{
$this->refundable = $refundable;
return $this;
}
/**
* Get refundable
*
* @return boolean
*/
public function getRefundable()
{
return $this->refundable;
}
/**
* Set cleared
*
* @param boolean $cleared
*
* @return KimaiExpenses
*/
public function setCleared($cleared)
{
$this->cleared = $cleared;
return $this;
}
/**
* Get cleared
*
* @return boolean
*/
public function getCleared()
{
return $this->cleared;
}
/**
* Set multiplier
*
* @param string $multiplier
*
* @return KimaiExpenses
*/
public function setMultiplier($multiplier)
{
$this->multiplier = $multiplier;
return $this;
}
/**
* Get multiplier
*
* @return string
*/
public function getMultiplier()
{
return $this->multiplier;
}
/**
* Set value
*
* @param string $value
*
* @return KimaiExpenses
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Get value
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Get expenseid
*
* @return integer
*/
public function getExpenseid()
{
return $this->expenseid;
}
}

View File

@@ -1,113 +0,0 @@
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiFixedrates
*
* @ORM\Table(name="fixedRates")
* @ORM\Entity
*/
class KimaiFixedrates
{
/**
* @var string
*
* @ORM\Column(name="rate", type="decimal", precision=10, scale=2, nullable=false)
*/
private $rate;
/**
* @var integer
*
* @ORM\Column(name="projectID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $projectid;
/**
* @var integer
*
* @ORM\Column(name="activityID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $activityid;
/**
* Set rate
*
* @param string $rate
*
* @return KimaiFixedrates
*/
public function setRate($rate)
{
$this->rate = $rate;
return $this;
}
/**
* Get rate
*
* @return string
*/
public function getRate()
{
return $this->rate;
}
/**
* Set projectid
*
* @param integer $projectid
*
* @return KimaiFixedrates
*/
public function setProjectid($projectid)
{
$this->projectid = $projectid;
return $this;
}
/**
* Get projectid
*
* @return integer
*/
public function getProjectid()
{
return $this->projectid;
}
/**
* Set activityid
*
* @param integer $activityid
*
* @return KimaiFixedrates
*/
public function setActivityid($activityid)
{
$this->activityid = $activityid;
return $this;
}
/**
* Get activityid
*
* @return integer
*/
public function getActivityid()
{
return $this->activityid;
}
}

View File

@@ -1,175 +0,0 @@
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiProjectsActivities
*
* @ORM\Table(name="projects_activities")
* @ORM\Entity
*/
class KimaiProjectsActivities
{
/**
* @var string
*
* @ORM\Column(name="budget", type="decimal", precision=10, scale=2, nullable=true)
*/
private $budget = '0.00';
/**
* @var string
*
* @ORM\Column(name="effort", type="decimal", precision=10, scale=2, nullable=true)
*/
private $effort;
/**
* @var string
*
* @ORM\Column(name="approved", type="decimal", precision=10, scale=2, nullable=true)
*/
private $approved;
/**
* @var integer
*
* @ORM\Column(name="projectID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $projectid;
/**
* @var integer
*
* @ORM\Column(name="activityID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $activityid;
/**
* Set budget
*
* @param string $budget
*
* @return KimaiProjectsActivities
*/
public function setBudget($budget)
{
$this->budget = $budget;
return $this;
}
/**
* Get budget
*
* @return string
*/
public function getBudget()
{
return $this->budget;
}
/**
* Set effort
*
* @param string $effort
*
* @return KimaiProjectsActivities
*/
public function setEffort($effort)
{
$this->effort = $effort;
return $this;
}
/**
* Get effort
*
* @return string
*/
public function getEffort()
{
return $this->effort;
}
/**
* Set approved
*
* @param string $approved
*
* @return KimaiProjectsActivities
*/
public function setApproved($approved)
{
$this->approved = $approved;
return $this;
}
/**
* Get approved
*
* @return string
*/
public function getApproved()
{
return $this->approved;
}
/**
* Set projectid
*
* @param integer $projectid
*
* @return KimaiProjectsActivities
*/
public function setProjectid($projectid)
{
$this->projectid = $projectid;
return $this;
}
/**
* Get projectid
*
* @return integer
*/
public function getProjectid()
{
return $this->projectid;
}
/**
* Set activityid
*
* @param integer $activityid
*
* @return KimaiProjectsActivities
*/
public function setActivityid($activityid)
{
$this->activityid = $activityid;
return $this;
}
/**
* Get activityid
*
* @return integer
*/
public function getActivityid()
{
return $this->activityid;
}
}

View File

@@ -1,146 +0,0 @@
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiRates
*
* @ORM\Table(name="rates")
* @ORM\Entity
*/
class KimaiRates
{
/**
* @var string
*
* @ORM\Column(name="rate", type="decimal", precision=10, scale=2, nullable=false)
*/
private $rate;
/**
* @var integer
*
* @ORM\Column(name="userID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $userid;
/**
* @var integer
*
* @ORM\Column(name="projectID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $projectid;
/**
* @var integer
*
* @ORM\Column(name="activityID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $activityid;
/**
* Set rate
*
* @param string $rate
*
* @return KimaiRates
*/
public function setRate($rate)
{
$this->rate = $rate;
return $this;
}
/**
* Get rate
*
* @return string
*/
public function getRate()
{
return $this->rate;
}
/**
* Set userid
*
* @param integer $userid
*
* @return KimaiRates
*/
public function setUserid($userid)
{
$this->userid = $userid;
return $this;
}
/**
* Get userid
*
* @return integer
*/
public function getUserid()
{
return $this->userid;
}
/**
* Set projectid
*
* @param integer $projectid
*
* @return KimaiRates
*/
public function setProjectid($projectid)
{
$this->projectid = $projectid;
return $this;
}
/**
* Get projectid
*
* @return integer
*/
public function getProjectid()
{
return $this->projectid;
}
/**
* Set activityid
*
* @param integer $activityid
*
* @return KimaiRates
*/
public function setActivityid($activityid)
{
$this->activityid = $activityid;
return $this;
}
/**
* Get activityid
*
* @return integer
*/
public function getActivityid()
{
return $this->activityid;
}
}

View File

@@ -1,66 +0,0 @@
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiStatuses
*
* @ORM\Table(name="statuses")
* @ORM\Entity
*/
class KimaiStatuses
{
/**
* @var string
*
* @ORM\Column(name="status", type="string", length=200, nullable=false)
*/
private $status;
/**
* @var boolean
*
* @ORM\Column(name="statusID", type="boolean")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $statusid;
/**
* Set status
*
* @param string $status
*
* @return KimaiStatuses
*/
public function setStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Get status
*
* @return string
*/
public function getStatus()
{
return $this->status;
}
/**
* Get statusid
*
* @return boolean
*/
public function getStatusid()
{
return $this->statusid;
}
}

View File

@@ -1,562 +0,0 @@
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiUsers
*
* @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"}), @ORM\UniqueConstraint(name="apikey", columns={"apikey"})})
* @ORM\Entity
*/
class KimaiUsers
{
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=160, nullable=false)
*/
private $name;
/**
* @var string
*
* @ORM\Column(name="alias", type="string", length=160, nullable=true)
*/
private $alias;
/**
* @var boolean
*
* @ORM\Column(name="trash", type="boolean", nullable=false)
*/
private $trash = '0';
/**
* @var boolean
*
* @ORM\Column(name="active", type="boolean", nullable=false)
*/
private $active = '1';
/**
* @var string
*
* @ORM\Column(name="mail", type="string", length=160, nullable=false)
*/
private $mail = '';
/**
* @var string
*
* @ORM\Column(name="password", type="string", length=254, nullable=true)
*/
private $password;
/**
* @var string
*
* @ORM\Column(name="passwordResetHash", type="string", length=32, nullable=true)
*/
private $passwordresethash;
/**
* @var integer
*
* @ORM\Column(name="ban", type="integer", nullable=false)
*/
private $ban = '0';
/**
* @var integer
*
* @ORM\Column(name="banTime", type="integer", nullable=false)
*/
private $bantime = '0';
/**
* @var string
*
* @ORM\Column(name="secure", type="string", length=60, nullable=false)
*/
private $secure = '0';
/**
* @var integer
*
* @ORM\Column(name="lastProject", type="integer", nullable=false)
*/
private $lastproject = '1';
/**
* @var integer
*
* @ORM\Column(name="lastActivity", type="integer", nullable=false)
*/
private $lastactivity = '1';
/**
* @var integer
*
* @ORM\Column(name="lastRecord", type="integer", nullable=false)
*/
private $lastrecord = '0';
/**
* @var string
*
* @ORM\Column(name="timeframeBegin", type="string", length=60, nullable=false)
*/
private $timeframebegin = '0';
/**
* @var string
*
* @ORM\Column(name="timeframeEnd", type="string", length=60, nullable=false)
*/
private $timeframeend = '0';
/**
* @var string
*
* @ORM\Column(name="apikey", type="string", length=30, nullable=true)
*/
private $apikey;
/**
* @var integer
*
* @ORM\Column(name="globalRoleID", type="integer", nullable=false)
*/
private $globalroleid;
/**
* @var integer
*
* @ORM\Column(name="userID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $userid;
/**
* Set name
*
* @param string $name
*
* @return KimaiUsers
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set alias
*
* @param string $alias
*
* @return KimaiUsers
*/
public function setAlias($alias)
{
$this->alias = $alias;
return $this;
}
/**
* Get alias
*
* @return string
*/
public function getAlias()
{
return $this->alias;
}
/**
* Set trash
*
* @param boolean $trash
*
* @return KimaiUsers
*/
public function setTrash($trash)
{
$this->trash = $trash;
return $this;
}
/**
* Get trash
*
* @return boolean
*/
public function getTrash()
{
return $this->trash;
}
/**
* Set active
*
* @param boolean $active
*
* @return KimaiUsers
*/
public function setActive($active)
{
$this->active = $active;
return $this;
}
/**
* Get active
*
* @return boolean
*/
public function getActive()
{
return $this->active;
}
/**
* Set mail
*
* @param string $mail
*
* @return KimaiUsers
*/
public function setMail($mail)
{
$this->mail = $mail;
return $this;
}
/**
* Get mail
*
* @return string
*/
public function getMail()
{
return $this->mail;
}
/**
* Set password
*
* @param string $password
*
* @return KimaiUsers
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set passwordresethash
*
* @param string $passwordresethash
*
* @return KimaiUsers
*/
public function setPasswordresethash($passwordresethash)
{
$this->passwordresethash = $passwordresethash;
return $this;
}
/**
* Get passwordresethash
*
* @return string
*/
public function getPasswordresethash()
{
return $this->passwordresethash;
}
/**
* Set ban
*
* @param integer $ban
*
* @return KimaiUsers
*/
public function setBan($ban)
{
$this->ban = $ban;
return $this;
}
/**
* Get ban
*
* @return integer
*/
public function getBan()
{
return $this->ban;
}
/**
* Set bantime
*
* @param integer $bantime
*
* @return KimaiUsers
*/
public function setBantime($bantime)
{
$this->bantime = $bantime;
return $this;
}
/**
* Get bantime
*
* @return integer
*/
public function getBantime()
{
return $this->bantime;
}
/**
* Set secure
*
* @param string $secure
*
* @return KimaiUsers
*/
public function setSecure($secure)
{
$this->secure = $secure;
return $this;
}
/**
* Get secure
*
* @return string
*/
public function getSecure()
{
return $this->secure;
}
/**
* Set lastproject
*
* @param integer $lastproject
*
* @return KimaiUsers
*/
public function setLastproject($lastproject)
{
$this->lastproject = $lastproject;
return $this;
}
/**
* Get lastproject
*
* @return integer
*/
public function getLastproject()
{
return $this->lastproject;
}
/**
* Set lastactivity
*
* @param integer $lastactivity
*
* @return KimaiUsers
*/
public function setLastactivity($lastactivity)
{
$this->lastactivity = $lastactivity;
return $this;
}
/**
* Get lastactivity
*
* @return integer
*/
public function getLastactivity()
{
return $this->lastactivity;
}
/**
* Set lastrecord
*
* @param integer $lastrecord
*
* @return KimaiUsers
*/
public function setLastrecord($lastrecord)
{
$this->lastrecord = $lastrecord;
return $this;
}
/**
* Get lastrecord
*
* @return integer
*/
public function getLastrecord()
{
return $this->lastrecord;
}
/**
* Set timeframebegin
*
* @param string $timeframebegin
*
* @return KimaiUsers
*/
public function setTimeframebegin($timeframebegin)
{
$this->timeframebegin = $timeframebegin;
return $this;
}
/**
* Get timeframebegin
*
* @return string
*/
public function getTimeframebegin()
{
return $this->timeframebegin;
}
/**
* Set timeframeend
*
* @param string $timeframeend
*
* @return KimaiUsers
*/
public function setTimeframeend($timeframeend)
{
$this->timeframeend = $timeframeend;
return $this;
}
/**
* Get timeframeend
*
* @return string
*/
public function getTimeframeend()
{
return $this->timeframeend;
}
/**
* Set apikey
*
* @param string $apikey
*
* @return KimaiUsers
*/
public function setApikey($apikey)
{
$this->apikey = $apikey;
return $this;
}
/**
* Get apikey
*
* @return string
*/
public function getApikey()
{
return $this->apikey;
}
/**
* Set globalroleid
*
* @param integer $globalroleid
*
* @return KimaiUsers
*/
public function setGlobalroleid($globalroleid)
{
$this->globalroleid = $globalroleid;
return $this;
}
/**
* Get globalroleid
*
* @return integer
*/
public function getGlobalroleid()
{
return $this->globalroleid;
}
/**
* Get userid
*
* @return integer
*/
public function getUserid()
{
return $this->userid;
}
}

View File

@@ -1,184 +0,0 @@
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* FIXME CAN BE REMOVED
*
* @ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
* @ORM\Table(name="demo_post")
*
* Defines the properties of the Post entity to represent the blog posts.
* See http://symfony.com/doc/current/book/doctrine.html#creating-an-entity-class
*
* Tip: if you have an existing database, you can generate these entity class automatically.
* See http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class Post
{
/**
* Use constants to define configuration options that rarely change instead
* of specifying them in app/config/config.yml.
* See http://symfony.com/doc/current/best_practices/configuration.html#constants-vs-configuration-options
*/
const NUM_ITEMS = 10;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
*/
private $title;
/**
* @ORM\Column(type="string")
*/
private $slug;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank(message="post.blank_summary")
*/
private $summary;
/**
* @ORM\Column(type="text")
* @Assert\NotBlank(message="post.blank_content")
* @Assert\Length(min = "10", minMessage = "post.too_short_content")
*/
private $content;
/**
* @ORM\Column(type="string")
* @Assert\Email()
*/
private $authorEmail;
/**
* @ORM\Column(type="datetime")
* @Assert\DateTime()
*/
private $publishedAt;
/**
* @ORM\OneToMany(
* targetEntity="Comment",
* mappedBy="post",
* orphanRemoval=true
* )
* @ORM\OrderBy({"publishedAt" = "DESC"})
*/
private $comments;
public function __construct()
{
$this->publishedAt = new \DateTime();
$this->comments = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getTitle()
{
return $this->title;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getSlug()
{
return $this->slug;
}
public function setSlug($slug)
{
$this->slug = $slug;
}
public function getContent()
{
return $this->content;
}
public function setContent($content)
{
$this->content = $content;
}
public function getAuthorEmail()
{
return $this->authorEmail;
}
public function setAuthorEmail($authorEmail)
{
$this->authorEmail = $authorEmail;
}
/**
* Is the given User the author of this Post?
*
* @param User $user
*
* @return bool
*/
public function isAuthor(User $user)
{
return $user->getEmail() === $this->getAuthorEmail();
}
public function getPublishedAt()
{
return $this->publishedAt;
}
public function setPublishedAt(\DateTime $publishedAt)
{
$this->publishedAt = $publishedAt;
}
public function getComments()
{
return $this->comments;
}
public function addComment(Comment $comment)
{
$this->comments->add($comment);
$comment->setPost($this);
}
public function removeComment(Comment $comment)
{
$this->comments->removeElement($comment);
}
public function getSummary()
{
return $this->summary;
}
public function setSummary($summary)
{
$this->summary = $summary;
}
}

View File

@@ -5,50 +5,499 @@ namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* FIXME CAN BE REMOVED
* KimaiUsers
*
* @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* @ORM\Table(name="demo_user")
*
* Defines the properties of the User entity to represent the application users.
* See http://symfony.com/doc/current/book/doctrine.html#creating-an-entity-class
*
* Tip: if you have an existing database, you can generate these entity class automatically.
* See http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
* @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"}), @ORM\UniqueConstraint(name="apikey", columns={"apikey"})})
* @ORM\Entity
*/
class User implements UserInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @ORM\Column(name="userID", type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", unique=true)
* @ORM\Column(name="name", type="string", length=160, nullable=false, unique=true)
*/
private $username;
/**
* @ORM\Column(type="string", unique=true)
* @ORM\Column(name="mail", type="string", length=160, nullable=false, unique=true)
*/
private $email;
/**
* @ORM\Column(type="string")
* @ORM\Column(name="password", type="string", length=254, nullable=true)
*/
private $password;
/**
* @var string
*
* @ORM\Column(name="alias", type="string", length=160, nullable=true)
*/
private $alias;
/**
* @var boolean
*
* @ORM\Column(name="trash", type="boolean", nullable=false)
*/
private $trash = '0';
/**
* @var boolean
*
* @ORM\Column(name="active", type="boolean", nullable=false)
*/
private $active = '1';
/**
* @var string
*
* @ORM\Column(name="passwordResetHash", type="string", length=32, nullable=true)
*/
private $passwordresethash;
/**
* @var integer
*
* @ORM\Column(name="ban", type="integer", nullable=false)
*/
private $ban = '0';
/**
* @var integer
*
* @ORM\Column(name="banTime", type="integer", nullable=false)
*/
private $bantime = '0';
/**
* @var string
*
* @ORM\Column(name="secure", type="string", length=60, nullable=false)
*/
private $secure = '0';
/**
* FIXME
*
* @var integer
*
* @ORM\Column(name="lastProject", type="integer", nullable=false)
*/
private $lastproject = '1';
/**
* FIXME
*
* @var integer
*
* @ORM\Column(name="lastActivity", type="integer", nullable=false)
*/
private $lastactivity = '1';
/**
* @var integer
*
* @ORM\Column(name="lastRecord", type="integer", nullable=false)
*/
private $lastrecord = '0';
/**
* @var string
*
* @ORM\Column(name="timeframeBegin", type="string", length=60, nullable=false)
*/
private $timeframebegin = '0';
/**
* @var string
*
* @ORM\Column(name="timeframeEnd", type="string", length=60, nullable=false)
*/
private $timeframeend = '0';
/**
* @var string
*
* @ORM\Column(name="apikey", type="string", length=30, nullable=true)
*/
private $apikey;
/**
* @ORM\Column(type="json_array")
*/
private $roles = [];
/**
* Set alias
*
* @param string $alias
*
* @return KimaiUsers
*/
public function setAlias($alias)
{
$this->alias = $alias;
return $this;
}
/**
* Get alias
*
* @return string
*/
public function getAlias()
{
return $this->alias;
}
/**
* Set trash
*
* @param boolean $trash
*
* @return KimaiUsers
*/
public function setTrash($trash)
{
$this->trash = $trash;
return $this;
}
/**
* Get trash
*
* @return boolean
*/
public function getTrash()
{
return $this->trash;
}
/**
* Set active
*
* @param boolean $active
*
* @return KimaiUsers
*/
public function setActive($active)
{
$this->active = $active;
return $this;
}
/**
* Get active
*
* @return boolean
*/
public function getActive()
{
return $this->active;
}
/**
* Set password
*
* @param string $password
*
* @return KimaiUsers
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set passwordresethash
*
* @param string $passwordresethash
*
* @return KimaiUsers
*/
public function setPasswordresethash($passwordresethash)
{
$this->passwordresethash = $passwordresethash;
return $this;
}
/**
* Get passwordresethash
*
* @return string
*/
public function getPasswordresethash()
{
return $this->passwordresethash;
}
/**
* Set ban
*
* @param integer $ban
*
* @return KimaiUsers
*/
public function setBan($ban)
{
$this->ban = $ban;
return $this;
}
/**
* Get ban
*
* @return integer
*/
public function getBan()
{
return $this->ban;
}
/**
* Set bantime
*
* @param integer $bantime
*
* @return KimaiUsers
*/
public function setBantime($bantime)
{
$this->bantime = $bantime;
return $this;
}
/**
* Get bantime
*
* @return integer
*/
public function getBantime()
{
return $this->bantime;
}
/**
* Set secure
*
* @param string $secure
*
* @return KimaiUsers
*/
public function setSecure($secure)
{
$this->secure = $secure;
return $this;
}
/**
* Get secure
*
* @return string
*/
public function getSecure()
{
return $this->secure;
}
/**
* Set lastproject
*
* @param integer $lastproject
*
* @return KimaiUsers
*/
public function setLastproject($lastproject)
{
$this->lastproject = $lastproject;
return $this;
}
/**
* Get lastproject
*
* @return integer
*/
public function getLastproject()
{
return $this->lastproject;
}
/**
* Set lastactivity
*
* @param integer $lastactivity
*
* @return KimaiUsers
*/
public function setLastactivity($lastactivity)
{
$this->lastactivity = $lastactivity;
return $this;
}
/**
* Get lastactivity
*
* @return integer
*/
public function getLastactivity()
{
return $this->lastactivity;
}
/**
* Set lastrecord
*
* @param integer $lastrecord
*
* @return KimaiUsers
*/
public function setLastrecord($lastrecord)
{
$this->lastrecord = $lastrecord;
return $this;
}
/**
* Get lastrecord
*
* @return integer
*/
public function getLastrecord()
{
return $this->lastrecord;
}
/**
* Set timeframebegin
*
* @param string $timeframebegin
*
* @return KimaiUsers
*/
public function setTimeframebegin($timeframebegin)
{
$this->timeframebegin = $timeframebegin;
return $this;
}
/**
* Get timeframebegin
*
* @return string
*/
public function getTimeframebegin()
{
return $this->timeframebegin;
}
/**
* Set timeframeend
*
* @param string $timeframeend
*
* @return KimaiUsers
*/
public function setTimeframeend($timeframeend)
{
$this->timeframeend = $timeframeend;
return $this;
}
/**
* Get timeframeend
*
* @return string
*/
public function getTimeframeend()
{
return $this->timeframeend;
}
/**
* Set apikey
*
* @param string $apikey
*
* @return KimaiUsers
*/
public function setApikey($apikey)
{
$this->apikey = $apikey;
return $this;
}
/**
* Get apikey
*
* @return string
*/
public function getApikey()
{
return $this->apikey;
}
/**
* Set globalroleid
*
* @param integer $globalroleid
*
* @return KimaiUsers
*/
public function setGlobalroleid($globalroleid)
{
$this->globalroleid = $globalroleid;
return $this;
}
/**
* Get globalroleid
*
* @return integer
*/
public function getGlobalroleid()
{
return $this->globalroleid;
}
public function getId()
{
return $this->id;
@@ -75,18 +524,6 @@ class User implements UserInterface
$this->email = $email;
}
/**
* {@inheritdoc}
*/
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
/**
* Returns the roles or permissions granted to the user for security.
*/
@@ -127,4 +564,9 @@ class User implements UserInterface
// if you had a plainPassword property, you'd nullify it here
// $this->plainPassword = null;
}
}
public function __toString()
{
return $this->getUsername();
}
}

View File

@@ -5,13 +5,30 @@ namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiPreferences
* UserPreference
*
* @ORM\Table(name="preferences")
* @ORM\Table(name="preferences",indexes={@ORM\Index(name="option_idx", columns={"userid", "option"})}))
* @ORM\Entity
*/
class KimaiPreferences
class UserPreference
{
/**
* @var integer
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(name="userID", type="integer")
*/
private $userid;
/**
* @var string
*
* @ORM\Column(name="option", type="string", length=255)
*/
private $option;
/**
* @var string
*
@@ -19,32 +36,12 @@ class KimaiPreferences
*/
private $value;
/**
* @var string
*
* @ORM\Column(name="option", type="string", length=255)
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $option;
/**
* @var integer
*
* @ORM\Column(name="userID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
*/
private $userid;
/**
* Set value
*
* @param string $value
*
* @return KimaiPreferences
* @return UserPreference
*/
public function setValue($value)
{
@@ -68,7 +65,7 @@ class KimaiPreferences
*
* @param string $option
*
* @return KimaiPreferences
* @return UserPreference
*/
public function setOption($option)
{
@@ -92,7 +89,7 @@ class KimaiPreferences
*
* @param integer $userid
*
* @return KimaiPreferences
* @return UserPreference
*/
public function setUserid($userid)
{

View File

@@ -11,45 +11,26 @@
namespace AppBundle\Event;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Component\EventDispatcher\Event;
use Avanzu\AdminThemeBundle\Model\MenuItemModel;
/**
* The ConfigureAdminMenuEvent is used for populating the administration navigation.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class ConfigureAdminMenuEvent extends Event
class ConfigureAdminMenuEvent extends ConfigureMenuEvent
{
const CONFIGURE = 'app.admin_menu_configure';
private $factory;
private $menu;
/**
* @param \Knp\Menu\FactoryInterface $factory
* @param \Knp\Menu\ItemInterface $menu
* This function will either return a MenuItem or null.
*
* In case this returns null, the user has not the ROLE_ADMIN.
*
* @return MenuItemModel|null
*/
public function __construct(FactoryInterface $factory, ItemInterface $menu)
public function getAdminMenu()
{
$this->factory = $factory;
$this->menu = $menu;
}
/**
* @return \Knp\Menu\FactoryInterface
*/
public function getFactory()
{
return $this->factory;
}
/**
* @return \Knp\Menu\ItemInterface
*/
public function getMenu()
{
return $this->menu;
return $this->getMenu()->getRootItem('admin');
}
}

View File

@@ -20,36 +20,7 @@ use Symfony\Component\EventDispatcher\Event;
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class ConfigureMainMenuEvent extends Event
class ConfigureMainMenuEvent extends ConfigureMenuEvent
{
const CONFIGURE = 'app.main_menu_configure';
private $factory;
private $menu;
/**
* @param \Knp\Menu\FactoryInterface $factory
* @param \Knp\Menu\ItemInterface $menu
*/
public function __construct(FactoryInterface $factory, ItemInterface $menu)
{
$this->factory = $factory;
$this->menu = $menu;
}
/**
* @return \Knp\Menu\FactoryInterface
*/
public function getFactory()
{
return $this->factory;
}
/**
* @return \Knp\Menu\ItemInterface
*/
public function getMenu()
{
return $this->menu;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Event;
use Avanzu\AdminThemeBundle\Model\MenuItemModel;
use Avanzu\AdminThemeBundle\Event\SidebarMenuEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
/**
* The ConfigureMenuEvent is used for populating navigations.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
abstract class ConfigureMenuEvent extends Event
{
/**
* @var Request
*/
private $request;
/**
* @var SidebarMenuEvent
*/
private $event;
/**
* @var AuthorizationChecker
*/
private $auth;
/**
* ConfigureMenuEvent constructor.
* @param AuthorizationChecker $auth
* @param Request $request
* @param MenuItemModel $menuModel
*/
public function __construct(
AuthorizationChecker $auth,
Request $request,
SidebarMenuEvent $event
)
{
$this->auth = $auth;
$this->request = $request;
$this->event = $event;
}
/**
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* @return SidebarMenuEvent
*/
public function getMenu()
{
return $this->event;
}
/**
* @return AuthorizationChecker
*/
public function getAuth()
{
return $this->auth;
}
}

View File

@@ -0,0 +1,120 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\EventListener;
use AppBundle\Event\ConfigureMainMenuEvent;
use AppBundle\Event\ConfigureAdminMenuEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Avanzu\AdminThemeBundle\Model\MenuItemModel;
use Avanzu\AdminThemeBundle\Event\SidebarMenuEvent;
/**
* Class MenuBuilder configures the main navigation.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class MenuBuilder
{
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var AuthorizationChecker
*/
private $security;
/**
* MenuBuilder constructor.
* @param EventDispatcherInterface $dispatcher
* @param AuthorizationChecker $security
*/
public function __construct(
EventDispatcherInterface $dispatcher,
AuthorizationChecker $security
)
{
$this->eventDispatcher = $dispatcher;
$this->security = $security;
}
/**
* Generate the main menu.
*
* @param SidebarMenuEvent $event
*/
public function onSetupNavbar(SidebarMenuEvent $event)
{
$request = $event->getRequest();
$isLoggedIn = $this->security->isGranted('IS_AUTHENTICATED_FULLY');
$isAdmin = $isLoggedIn && $this->security->isGranted('ROLE_ADMIN');
$event->addItem(
new MenuItemModel('dashboard', 'menu.homepage', 'dashboard', [], 'fa fa-home')
);
$this->eventDispatcher->dispatch(
ConfigureMainMenuEvent::CONFIGURE,
new ConfigureMainMenuEvent(
$this->security,
$request,
$event
)
);
if ($isAdmin) {
$event->addItem(
new MenuItemModel('admin', 'menu.admin_dashboard', '', [], 'fa fa-dashboard')
);
$this->eventDispatcher->dispatch(
ConfigureAdminMenuEvent::CONFIGURE,
new ConfigureAdminMenuEvent(
$this->security,
$request,
$event
)
);
}
$event->addItem(
new MenuItemModel('logout', 'menu.logout', 'security_logout', [], 'fa fa-sign-out')
);
$this->activateByRoute(
$event->getRequest()->get('_route'),
$event->getItems()
);
}
/**
* @param string $route
* @param MenuItemModel[] $items
*/
protected function activateByRoute($route, $items)
{
foreach($items as $item) {
if($item->hasChildren()) {
$this->activateByRoute($route, $item->getChildren());
}
else {
if($item->getRoute() == $route) {
$item->setIsActive(true);
}
}
}
}
}

View File

@@ -1,58 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Form;
use AppBundle\Entity\Comment;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* FIXME CAN BE REMOVED
*
* Defines the form used to create and manipulate blog comments. Although in this
* case the form is trivial and we could build it inside the controller, a good
* practice is to always define your forms as classes.
* See http://symfony.com/doc/current/book/forms.html#creating-form-classes
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class CommentType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// By default, form fields include the 'required' attribute, which enables
// the client-side form validation. This means that you can't test the
// server-side validation errors from the browser. To temporarily disable
// this validation, set the 'required' attribute to 'false':
//
// $builder->add('content', null, ['required' => false]);
$builder
->add('content')
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Comment::class,
]);
}
}

View File

@@ -1,76 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Form;
use AppBundle\Entity\Post;
use AppBundle\Form\Type\DateTimePickerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* FIXME CAN BE REMOVED
*
* Defines the form used to create and manipulate blog posts.
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class PostType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// For the full reference of options defined by each form field type
// see http://symfony.com/doc/current/reference/forms/types.html
// By default, form fields include the 'required' attribute, which enables
// the client-side form validation. This means that you can't test the
// server-side validation errors from the browser. To temporarily disable
// this validation, set the 'required' attribute to 'false':
//
// $builder->add('title', null, ['required' => false, ...]);
$builder
->add('title', null, [
'attr' => ['autofocus' => true],
'label' => 'label.title',
])
->add('summary', TextareaType::class, [
'label' => 'label.summary',
])
->add('content', null, [
'attr' => ['rows' => 20],
'label' => 'label.content',
])
->add('authorEmail', null, [
'label' => 'label.author_email',
])
->add('publishedAt', DateTimePickerType::class, [
'label' => 'label.published_at',
])
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Post::class,
]);
}
}

View File

@@ -1,129 +0,0 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
use AppBundle\Event\ConfigureMainMenuEvent;
use AppBundle\Event\ConfigureAdminMenuEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
/**
* Class MenuBuilder
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class MenuBuilder
{
/**
* @var FactoryInterface
*/
private $factory;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var AuthorizationChecker
*/
private $security;
/**
* MenuBuilder constructor.
* @param FactoryInterface $factory
* @param EventDispatcherInterface $dispatcher
* @param AuthorizationChecker $security
*/
public function __construct(FactoryInterface $factory,
EventDispatcherInterface $dispatcher,
AuthorizationChecker $security)
{
$this->factory = $factory;
$this->eventDispatcher = $dispatcher;
$this->security = $security;
}
/**
* Generate the main menu.
*
* @param array $options
* @return \Knp\Menu\ItemInterface
*/
public function createMainMenu(array $options)
{
$menu = $this->factory->createItem('main');
$menu->setChildrenAttribute('class', 'nav navbar-nav navbar-right');
$isLoggedIn = $this->security->isGranted('IS_AUTHENTICATED_FULLY');
$isAdmin = $isLoggedIn && $this->security->isGranted('ROLE_ADMIN');
if ($isLoggedIn) {
$item = $menu->addChild('Homepage', array('route' => 'blog_index'));
$item->setLabel('menu.homepage');
$item->setChildrenAttribute('icon', 'home');
}
$this->eventDispatcher->dispatch(
ConfigureMainMenuEvent::CONFIGURE,
new ConfigureMainMenuEvent($this->factory, $menu)
);
if ($isAdmin) {
$item = $menu->addChild('Administration', array('route' => 'admin_post_index'));
$item->setLabel('menu.admin');
$item->setChildrenAttribute('icon', 'lock');
}
if ($isLoggedIn) {
$item = $menu->addChild('Logout', array('route' => 'security_logout'));
$item->setLabel('menu.logout');
$item->setChildrenAttribute('icon', 'sign-out');
}
return $menu;
}
/**
* Generate the admin menu.
*
* @param array $options
* @return \Knp\Menu\ItemInterface
*/
public function createAdminMenu(array $options)
{
$menu = $this->factory->createItem('admin');
$menu->setChildrenAttribute('class', 'nav navbar-nav navbar-right');
$isLoggedIn = $this->security->isGranted('IS_AUTHENTICATED_FULLY');
$isAdmin = $isLoggedIn && $this->security->isGranted('ROLE_ADMIN');
// FIXME CAN BE REMOVED
if ($isAdmin) {
$item = $menu->addChild('Post list', array('route' => 'admin_post_index'));
$item->setLabel('menu.post_list');
$item->setChildrenAttribute('icon', 'list-alt');
}
$this->eventDispatcher->dispatch(
ConfigureAdminMenuEvent::CONFIGURE,
new ConfigureAdminMenuEvent($this->factory, $menu)
);
if ($isLoggedIn) {
$item = $menu->addChild('Logout', array('route' => 'security_logout'));
$item->setLabel('menu.logout');
$item->setChildrenAttribute('icon', 'sign-out');
}
return $menu;
}
}

View File

@@ -1,61 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Repository;
use AppBundle\Entity\Post;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
/**
* FIXME CAN BE REMOVED
*
* This custom Doctrine repository contains some methods which are useful when
* querying for blog post information.
* See http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class PostRepository extends EntityRepository
{
/**
* @return Query
*/
public function queryLatest()
{
return $this->getEntityManager()
->createQuery('
SELECT p
FROM AppBundle:Post p
WHERE p.publishedAt <= :now
ORDER BY p.publishedAt DESC
')
->setParameter('now', new \DateTime())
;
}
/**
* @param int $page
*
* @return Pagerfanta
*/
public function findLatest($page = 1)
{
$paginator = new Pagerfanta(new DoctrineORMAdapter($this->queryLatest(), false));
$paginator->setMaxPerPage(Post::NUM_ITEMS);
$paginator->setCurrentPage($page);
return $paginator;
}
}

View File

@@ -14,7 +14,6 @@ namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* FIXME CAN BE REMOVED
*
* This custom Doctrine repository is empty because so far we don't need any custom
* method to query for application user information. But it's always a good practice

View File

@@ -1,75 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Tests\Controller\Admin;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Response;
/**
* FIXME CAN BE REMOVED
*
* Functional test for the controllers defined inside the BlogController used
* for managing the blog in the backend.
* See http://symfony.com/doc/current/book/testing.html#functional-tests
*
* Whenever you test resources protected by a firewall, consider using the
* technique explained in:
* http://symfony.com/doc/current/cookbook/testing/http_authentication.html
*
* Execute the application tests using this command (requires PHPUnit to be installed):
*
* $ cd your-symfony-project/
* $ phpunit -c app
*
*/
class BlogControllerTest extends WebTestCase
{
public function testRegularUsersCannotAccessToTheBackend()
{
$client = static::createClient([], [
'PHP_AUTH_USER' => 'john_user',
'PHP_AUTH_PW' => 'kitten',
]);
$client->request('GET', '/en/admin/post/');
$this->assertEquals(Response::HTTP_FORBIDDEN, $client->getResponse()->getStatusCode());
}
public function testAdministratorUsersCanAccessToTheBackend()
{
$client = static::createClient([], [
'PHP_AUTH_USER' => 'anna_admin',
'PHP_AUTH_PW' => 'kitten',
]);
$client->request('GET', '/en/admin/post/');
$this->assertEquals(Response::HTTP_OK, $client->getResponse()->getStatusCode());
}
public function testIndex()
{
$client = static::createClient([], [
'PHP_AUTH_USER' => 'anna_admin',
'PHP_AUTH_PW' => 'kitten',
]);
$crawler = $client->request('GET', '/en/admin/post/');
$this->assertCount(
30,
$crawler->filter('body#admin_post_index #main tbody tr'),
'The backend homepage displays all the available posts.'
);
}
}

View File

@@ -1,42 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use AppBundle\Entity\Post;
/**
* FIXME CAN BE REMOVED
*
* Functional test for the controllers defined inside BlogController.
* See http://symfony.com/doc/current/book/testing.html#functional-tests
*
* Execute the application tests using this command (requires PHPUnit to be installed):
*
* $ cd your-symfony-project/
* $ phpunit -c app
*
*/
class BlogControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/en/blog/');
$this->assertCount(
Post::NUM_ITEMS,
$crawler->filter('article.post'),
'The homepage displays the right number of posts.'
);
}
}

View File

@@ -55,6 +55,7 @@ class DefaultControllerTest extends WebTestCase
*/
public function testSecureUrls($url)
{
$this->markTestSkipped('No admin URLs for testing');
$client = self::createClient();
$client->request('GET', $url);
@@ -70,16 +71,11 @@ class DefaultControllerTest extends WebTestCase
public function getPublicUrls()
{
yield ['/'];
yield ['/en/blog/'];
yield ['/en/blog/posts/morbi-tempus-commodo-mattis'];
yield ['/en/login'];
}
public function getSecureUrls()
{
yield ['/en/admin/post/'];
yield ['/en/admin/post/new'];
yield ['/en/admin/post/1'];
yield ['/en/admin/post/1/edit'];
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Controller\Admin;
use TimesheetBundle\Entity\Timesheet;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* Controller used for manage timesheet entries in the admin part of the site.
*
* @Route("/admin/timesheet")
* @Security("has_role('ROLE_CUSTOMER')")
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class TimesheetController extends Controller
{
/**
* @Route("/", defaults={"page": 1}, name="admin_timesheet_index")
* @Route("/timesheet/{page}", requirements={"page": "[1-9]\d*"}, name="admin_timesheet_index_paginated")
* @Method("GET")
* @Cache(smaxage="10")
*/
public function indexAction($page)
{
$entries = $this->getDoctrine()->getRepository(Timesheet::class)->findLatest($page);
return $this->render('TimesheetBundle:timesheet:index.html.twig', ['entries' => $entries]);
}
}

View File

@@ -44,7 +44,12 @@ class LoadFixtures extends AppBundleLoadFixtures
private function loadTimesheet(ObjectManager $manager)
{
$amountUsers = count($manager->getRepository(User::class)->findAll());
$allUsers = [];
$users = $manager->getRepository(User::class)->findAll();
foreach ($users as $user) {
$allUsers[$user->getId()] = $user;
}
$amountUsers = count($allUsers);
for ($i = 0; $i <= self::AMOUNT_TIMESHEET; $i++) {
@@ -67,7 +72,7 @@ class LoadFixtures extends AppBundleLoadFixtures
$entry->setStart($start->getTimestamp());
$entry->setEnd($end->getTimestamp());
$entry->setDuration($end->modify('- ' . $start->getTimestamp() . ' seconds')->getTimestamp());
$entry->setUserid(rand(1, $amountUsers));
$entry->setUser($allUsers[rand(1, $amountUsers)]);
//$entry->setApproved(false); // TODO
//$entry->setFixedrate(); // TODO
//$entry->setRate(); // TODO

View File

@@ -1,17 +1,38 @@
<?php
namespace AppBundle\Entity;
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiActivities
* Activity
*
* @ORM\Table(name="activities")
* @ORM\Entity
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class KimaiActivities
class Activity
{
/**
* @var integer
*
* @ORM\Column(name="activityID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $activityid;
/**
* @var string
*
@@ -47,23 +68,12 @@ class KimaiActivities
*/
private $trash = '0';
/**
* @var integer
*
* @ORM\Column(name="activityID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $activityid;
/**
* Set name
*
* @param string $name
*
* @return KimaiActivities
* @return Activity
*/
public function setName($name)
{
@@ -87,7 +97,7 @@ class KimaiActivities
*
* @param string $comment
*
* @return KimaiActivities
* @return Activity
*/
public function setComment($comment)
{
@@ -111,7 +121,7 @@ class KimaiActivities
*
* @param boolean $visible
*
* @return KimaiActivities
* @return Activity
*/
public function setVisible($visible)
{
@@ -135,7 +145,7 @@ class KimaiActivities
*
* @param boolean $filter
*
* @return KimaiActivities
* @return Activity
*/
public function setFilter($filter)
{
@@ -159,7 +169,7 @@ class KimaiActivities
*
* @param boolean $trash
*
* @return KimaiActivities
* @return Activity
*/
public function setTrash($trash)
{

View File

@@ -1,17 +1,38 @@
<?php
namespace AppBundle\Entity;
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiCustomers
* Customer
*
* @ORM\Table(name="customers")
* @ORM\Entity
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class KimaiCustomers
class Customer
{
/**
* @var integer
*
* @ORM\Column(name="customerID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $customerid;
/**
* @var string
*
@@ -19,27 +40,6 @@ class KimaiCustomers
*/
private $name;
/**
* @var string
*
* @ORM\Column(name="password", type="string", length=255, nullable=true)
*/
private $password;
/**
* @var string
*
* @ORM\Column(name="passwordResetHash", type="string", length=32, nullable=true)
*/
private $passwordresethash;
/**
* @var string
*
* @ORM\Column(name="secure", type="string", length=60, nullable=false)
*/
private $secure = '0';
/**
* @var string
*
@@ -159,23 +159,12 @@ class KimaiCustomers
*/
private $trash = '0';
/**
* @var integer
*
* @ORM\Column(name="customerID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $customerid;
/**
* Set name
*
* @param string $name
*
* @return KimaiCustomers
* @return Customer
*/
public function setName($name)
{
@@ -199,7 +188,7 @@ class KimaiCustomers
*
* @param string $password
*
* @return KimaiCustomers
* @return Customer
*/
public function setPassword($password)
{
@@ -223,7 +212,7 @@ class KimaiCustomers
*
* @param string $passwordresethash
*
* @return KimaiCustomers
* @return Customer
*/
public function setPasswordresethash($passwordresethash)
{
@@ -247,7 +236,7 @@ class KimaiCustomers
*
* @param string $secure
*
* @return KimaiCustomers
* @return Customer
*/
public function setSecure($secure)
{
@@ -271,7 +260,7 @@ class KimaiCustomers
*
* @param string $comment
*
* @return KimaiCustomers
* @return Customer
*/
public function setComment($comment)
{
@@ -295,7 +284,7 @@ class KimaiCustomers
*
* @param boolean $visible
*
* @return KimaiCustomers
* @return Customer
*/
public function setVisible($visible)
{
@@ -319,7 +308,7 @@ class KimaiCustomers
*
* @param boolean $filter
*
* @return KimaiCustomers
* @return Customer
*/
public function setFilter($filter)
{
@@ -343,7 +332,7 @@ class KimaiCustomers
*
* @param string $company
*
* @return KimaiCustomers
* @return Customer
*/
public function setCompany($company)
{
@@ -367,7 +356,7 @@ class KimaiCustomers
*
* @param string $vat
*
* @return KimaiCustomers
* @return Customer
*/
public function setVat($vat)
{
@@ -391,7 +380,7 @@ class KimaiCustomers
*
* @param string $contact
*
* @return KimaiCustomers
* @return Customer
*/
public function setContact($contact)
{
@@ -415,7 +404,7 @@ class KimaiCustomers
*
* @param string $street
*
* @return KimaiCustomers
* @return Customer
*/
public function setStreet($street)
{
@@ -439,7 +428,7 @@ class KimaiCustomers
*
* @param string $zipcode
*
* @return KimaiCustomers
* @return Customer
*/
public function setZipcode($zipcode)
{
@@ -463,7 +452,7 @@ class KimaiCustomers
*
* @param string $city
*
* @return KimaiCustomers
* @return Customer
*/
public function setCity($city)
{
@@ -487,7 +476,7 @@ class KimaiCustomers
*
* @param string $country
*
* @return KimaiCustomers
* @return Customer
*/
public function setCountry($country)
{
@@ -511,7 +500,7 @@ class KimaiCustomers
*
* @param string $phone
*
* @return KimaiCustomers
* @return Customer
*/
public function setPhone($phone)
{
@@ -535,7 +524,7 @@ class KimaiCustomers
*
* @param string $fax
*
* @return KimaiCustomers
* @return Customer
*/
public function setFax($fax)
{
@@ -559,7 +548,7 @@ class KimaiCustomers
*
* @param string $mobile
*
* @return KimaiCustomers
* @return Customer
*/
public function setMobile($mobile)
{
@@ -583,7 +572,7 @@ class KimaiCustomers
*
* @param string $mail
*
* @return KimaiCustomers
* @return Customer
*/
public function setMail($mail)
{
@@ -607,7 +596,7 @@ class KimaiCustomers
*
* @param string $homepage
*
* @return KimaiCustomers
* @return Customer
*/
public function setHomepage($homepage)
{
@@ -631,7 +620,7 @@ class KimaiCustomers
*
* @param string $timezone
*
* @return KimaiCustomers
* @return Customer
*/
public function setTimezone($timezone)
{
@@ -655,7 +644,7 @@ class KimaiCustomers
*
* @param boolean $trash
*
* @return KimaiCustomers
* @return Customer
*/
public function setTrash($trash)
{

View File

@@ -1,17 +1,38 @@
<?php
namespace AppBundle\Entity;
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* KimaiProjects
* Project
*
* @ORM\Table(name="projects", indexes={@ORM\Index(name="customerID", columns={"customerID"})})
* @ORM\Entity
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class KimaiProjects
class Project
{
/**
* @var integer
*
* @ORM\Column(name="projectID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $projectid;
/**
* @var integer
*
@@ -82,17 +103,6 @@ class KimaiProjects
*/
private $internal = '0';
/**
* @var integer
*
* @ORM\Column(name="projectID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $projectid;
/**
* Set customerid
*

View File

@@ -11,13 +11,14 @@
namespace TimesheetBundle\Entity;
use AppBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
/**
* Timesheet entity.
*
* @ORM\Entity(repositoryClass="TimesheetBundle\Repository\TimesheetRepository")
* @ORM\Table(name="timeSheet", indexes={@ORM\Index(name="userID", columns={"userID"}), @ORM\Index(name="projectID", columns={"projectID"}), @ORM\Index(name="activityID", columns={"activityID"})})
* @ORM\Table(name="timeSheet", indexes={@ORM\Index(columns={"userID"}), @ORM\Index(columns={"projectID"}), @ORM\Index(name="activityID", columns={"activityID"})})
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
@@ -47,9 +48,10 @@ class Timesheet
/**
* @var integer
*
* @ORM\Column(name="userID", type="integer", nullable=false)
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* @ORM\JoinColumn(name="userID", referencedColumnName="userID")
*/
private $userid;
private $user;
/**
* @var integer
@@ -165,7 +167,7 @@ class Timesheet
*
* @param integer $start
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setStart($start)
{
@@ -189,7 +191,7 @@ class Timesheet
*
* @param integer $end
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setEnd($end)
{
@@ -213,7 +215,7 @@ class Timesheet
*
* @param integer $duration
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setDuration($duration)
{
@@ -233,27 +235,27 @@ class Timesheet
}
/**
* Set userid
* Set user
*
* @param integer $userid
* @param User $user
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setUserid($userid)
public function setUser(User $user)
{
$this->userid = $userid;
$this->user = $user;
return $this;
}
/**
* Get userid
* Get user
*
* @return integer
* @return User
*/
public function getUserid()
public function getUser()
{
return $this->userid;
return $this->user;
}
/**
@@ -261,7 +263,7 @@ class Timesheet
*
* @param integer $projectid
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setProjectid($projectid)
{
@@ -285,7 +287,7 @@ class Timesheet
*
* @param integer $activityid
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setActivityid($activityid)
{
@@ -309,7 +311,7 @@ class Timesheet
*
* @param string $description
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setDescription($description)
{
@@ -333,7 +335,7 @@ class Timesheet
*
* @param string $comment
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setComment($comment)
{
@@ -357,7 +359,7 @@ class Timesheet
*
* @param boolean $commenttype
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setCommenttype($commenttype)
{
@@ -381,7 +383,7 @@ class Timesheet
*
* @param boolean $cleared
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setCleared($cleared)
{
@@ -405,7 +407,7 @@ class Timesheet
*
* @param string $location
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setLocation($location)
{
@@ -429,7 +431,7 @@ class Timesheet
*
* @param string $trackingnumber
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setTrackingnumber($trackingnumber)
{
@@ -453,7 +455,7 @@ class Timesheet
*
* @param string $rate
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setRate($rate)
{
@@ -477,7 +479,7 @@ class Timesheet
*
* @param string $fixedrate
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setFixedrate($fixedrate)
{
@@ -501,7 +503,7 @@ class Timesheet
*
* @param string $budget
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setBudget($budget)
{
@@ -525,7 +527,7 @@ class Timesheet
*
* @param string $approved
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setApproved($approved)
{
@@ -549,7 +551,7 @@ class Timesheet
*
* @param integer $statusid
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setStatusid($statusid)
{
@@ -573,7 +575,7 @@ class Timesheet
*
* @param boolean $billable
*
* @return KimaiTimesheet
* @return Timesheet
*/
public function setBillable($billable)
{

View File

@@ -13,9 +13,12 @@ namespace TimesheetBundle\EventListener;
use AppBundle\Event\ConfigureMainMenuEvent;
use AppBundle\Event\ConfigureAdminMenuEvent;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Avanzu\AdminThemeBundle\Model\MenuItemModel;
use Avanzu\AdminThemeBundle\Event\SidebarMenuEvent;
/**
* Class Menu
* Menus for timesheet
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
@@ -27,10 +30,18 @@ class Menu
public function onMainMenuConfigure(ConfigureMainMenuEvent $event)
{
$menu = $event->getMenu();
$auth = $event->getAuth();
$item = $menu->addChild('Timesheet', array('route' => 'timesheet'));
$item->setLabel('menu.timesheet');
$item->setChildrenAttribute('icon', 'clock-o');
$isLoggedIn = $auth->isGranted('IS_AUTHENTICATED_FULLY');
$isAdmin = $isLoggedIn && $auth->isGranted('ROLE_ADMIN');
if (!$isLoggedIn) {
return;
}
$menu->addItem(
new MenuItemModel('timesheet', 'menu.timesheet', 'timesheet', [], 'fa fa-clock-o')
);
}
/**
@@ -38,10 +49,18 @@ class Menu
*/
public function onAdminMenuConfigure(ConfigureAdminMenuEvent $event)
{
$menu = $event->getMenu();
$menu = $event->getAdminMenu();
$auth = $event->getAuth();
$item = $menu->addChild('TimeAdmin', array('route' => 'timesheet'));
//$item->setLabel('menu.admin_timesheet');
$item->setChildrenAttribute('icon', 'clock-o');
$isLoggedIn = $auth->isGranted('IS_AUTHENTICATED_FULLY');
$isAdmin = $isLoggedIn && $auth->isGranted('ROLE_ADMIN');
if (!$isAdmin) {
return;
}
$menu->addChild(
new MenuItemModel('timesheet_admin', 'menu.admin_timesheet', 'admin_timesheet_index', [], 'fa fa-clock-o')
);
}
}

View File

@@ -1,34 +1,34 @@
{% extends 'base.html.twig' %}
{% block body_id 'timesheet_index' %}
{% block page_title %}Timesheet{% endblock %}
{% block page_subtitle %}manage your times{% endblock %}
{% block main %}
{% if entries %}
<table class="table table-striped">
<thead>
<tr>
<th><i class="fa fa-calendar"></i> {{ 'label.date'|trans }}</th>
<th><i class="fa fa-circle-o"></i> {{ 'label.start'|trans }}</th>
<th><i class="fa fa-circle"></i> {{ 'label.end'|trans }}</th>
<th><i class="fa fa-clock-o"></i> {{ 'label.duration'|trans }}</th>
<th><i class="fa fa-user"></i> {{ 'label.user'|trans }}</th>
<th>{{ 'label.description'|trans }}</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<table class="table table-striped">
<thead>
<tr>
<td>{{ entry.start|date("m/d/Y") }}</td>
<td>{{ entry.start|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|gmdate }}</td>
<td>{{ entry.userid }}</td>
<td>{{ entry.description }}</td>
<th><i class="fa fa-calendar"></i> {{ 'label.date'|trans }}</th>
<th><i class="fa fa-circle-o"></i> {{ 'label.start'|trans }}</th>
<th><i class="fa fa-circle"></i> {{ 'label.end'|trans }}</th>
<th><i class="fa fa-clock-o"></i> {{ 'label.duration'|trans }}</th>
<th><i class="fa fa-user"></i> {{ 'label.user'|trans }}</th>
<th>{{ 'label.description'|trans }}</th>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td>{{ entry.start|date("m/d/Y") }}</td>
<td>{{ entry.start|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|gmdate }}</td>
<td>{{ entry.user.username }}</td>
<td>{{ entry.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="well">{{ 'post.no_posts_found'|trans }}</div>
{% endif %}
@@ -37,7 +37,3 @@
{{ pagerfanta(entries, 'twitter_bootstrap3_translated', { routeName: 'timesheet_paginated' }) }}
</div>
{% endblock %}
{% block sidebar %}
{{ parent() }}
{% endblock %}

View File

@@ -12,6 +12,7 @@
namespace TimesheetBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use TimesheetBundle\EventListener\Menu as MenuListener;
/**
* This class defines the Bundle for all Timesheet related topics
@@ -20,4 +21,14 @@ use Symfony\Component\HttpKernel\Bundle\Bundle;
*/
class TimesheetBundle extends Bundle
{
/**
* Boots the Bundle.
*/
public function boot()
{
$listener = new MenuListener();
/* @var $dispatcher \Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher */
$dispatcher = $this->container->get('event_dispatcher');
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB