Skip to content

Commit de09ec9

Browse files
committed
* update readme, license
* introduce modern structure
1 parent 8d5a338 commit de09ec9

File tree

13 files changed

+253
-78
lines changed

13 files changed

+253
-78
lines changed

.env.example

-3
This file was deleted.

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
.env
2-
vendor
1+
vendor/
2+
app/config/config.php

CONTRIBUTING.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Contributions are welcome
2+
3+
* just create a PR on Github and let me know

LICENSE

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2+
Version 2, December 2004
3+
4+
Copyright (C) 2004 Sam Hocevar <[email protected]>
5+
6+
Everyone is permitted to copy and distribute verbatim or modified
7+
copies of this license document, and changing it is allowed as long
8+
as the name is changed.
9+
10+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12+
13+
0. You just DO WHAT THE FUCK YOU WANT TO.

README.md

+38-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,40 @@
1-
## How To Start
2-
* create `.env` file in the root
1+
# Rationale
2+
Surprised to see your key colleague ran out for vacation again?
3+
Yes, *there was an email about it 2 months ago*..
4+
Usually we forget about it same minute, and realize the fact when it is too late.
5+
You don't want another vacation to ruin your sprint again, do you?
6+
7+
Then welcome a solution to **sync all vacations from any HR system to any Calendar**, so you always know who is going to be AFK!
8+
9+
# How To Start
10+
* install dependencies
311
```bash
4-
copy .env.example .env
5-
nano .env
12+
composer install
613
```
7-
* finally, put the script to be run every day. It will export all vacations to your Outlook Calendar.
14+
* create and fill `app/config/config.php` file
15+
```bash
16+
cp app/config/config.example.php app/config/config.php
17+
nano app/config/config.php
18+
```
19+
* finally, put the script to be run every minute. It will export all vacations to your Outlook Calendar.
20+
```php
21+
php sync.php
22+
```
23+
24+
# Security concerns
25+
MS Exchange API (known as Exchange Web Services) requires plain-text user password to be specified.
26+
To workaround this, the password is requested when the script starts. So, it is never stored on disk or github as plain text.
27+
Thus, the solution is enterprise ready and 100% IT-security compliant.
28+
29+
# Known Limitations
30+
Only [Securex HR Online](https://www.securex.lu/en/our-it-tool-hronline/9) -> MS Exchange Calendar is supported.
31+
32+
# TODO
33+
* delete vacation events from Calendar if they disappear from Securex
34+
* support custom observable periods (current hardcoded to 1 month)
35+
* support more Calendars - Google Calendar, Zoho Calendar, etc.
36+
* support more ATS - BambooHR, Recruitee, Manatal, Oracle Taleo, etc.
37+
* tests
38+
39+
# LICENSE
40+
See LICENSE file

app/config/config.example.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types=1);
2+
3+
use jamesiarmes\PhpEws\Client;
4+
5+
return [
6+
'email' => '[email protected]',
7+
'plain_text_password' => 'secret',
8+
'team' => [
9+
'[email protected]' => 'Developer',
10+
'[email protected]' => 'Scrum Master',
11+
'[email protected]' => 'Team Lead',
12+
],
13+
'ms_exchange_host' => 'webmail.company.com',
14+
'ms_exchange_version' => Client::VERSION_2016, // must be a constant from vendor/php-ews/php-ews/src/Client.php
15+
];

composer.json

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
{
2-
"name": "ivastly/securex-outlook-intgrtn",
2+
"name": "ivastly/ats-calendar-integration",
33
"type": "project",
4-
"description": "Exports vacations from securex (securexhrservices.eu) to MS Outlook calendar",
4+
"description": "Exports vacations from any ATS to any Calendar system, e.g. Securex => MS Outlook Calendar",
55
"keywords": [
66
"outlook",
77
"securex",
88
"securexhrservices",
9-
"Graph API"
9+
"ews"
1010
],
1111
"license": "WTFPL",
1212
"require": {
13-
"php-ews/php-ews": "~1.0",
14-
"symfony/dotenv": "^4.3"
13+
"php": "^7.3",
14+
"php-ews/php-ews": "~1.0"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"Src\\": "src"
19+
}
1520
}
1621
}

composer.lock

+1-58
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Src\Calendar\MsExchange\Business\Domain;
4+
5+
use DateTime;
6+
7+
class Event
8+
{
9+
/** @var string */
10+
private $title;
11+
12+
/** @var DateTime */
13+
private $startDate;
14+
15+
/** @var DateTime */
16+
private $endDate;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Src\Calendar\DataAccess\Api;
4+
5+
interface CalendarClientInterface
6+
{
7+
/**
8+
* @return Event[]
9+
*/
10+
public function searchForVacationEvents(): array;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Src\MsExchange;
4+
5+
use DateTime;
6+
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseFolderIdsType;
7+
use jamesiarmes\PhpEws\Client as PhpEwsClient;
8+
use jamesiarmes\PhpEws\Enumeration\DefaultShapeNamesType;
9+
use jamesiarmes\PhpEws\Enumeration\DistinguishedFolderIdNameType;
10+
use jamesiarmes\PhpEws\Enumeration\ResponseClassType;
11+
use jamesiarmes\PhpEws\Request\FindItemType;
12+
use jamesiarmes\PhpEws\Type\CalendarViewType;
13+
use jamesiarmes\PhpEws\Type\DistinguishedFolderIdType;
14+
use jamesiarmes\PhpEws\Type\ItemResponseShapeType;
15+
use Src\Calendar\DataAccess\Api\CalendarClientInterface;
16+
use Src\Config;
17+
18+
class Client implements CalendarClientInterface
19+
{
20+
/** @var PhpEwsClient */
21+
private $client;
22+
23+
public function __construct(Config $config)
24+
{
25+
$this->client =
26+
new PhpEwsClient(
27+
$config->getMsExchangeHost(),
28+
$config->getEmail(),
29+
$config->getPassword(),
30+
$config->getMsExchangeVersion()
31+
);
32+
}
33+
34+
public function searchForVacationEvents(): array
35+
{
36+
$startDate = new DateTime('now');
37+
$endDate = new DateTime('+1 month');
38+
$timezone = 'E. Europe Standard Time';
39+
$this->client->setTimezone($timezone);
40+
$request = new FindItemType();
41+
$request->ParentFolderIds = new NonEmptyArrayOfBaseFolderIdsType();
42+
$request->ItemShape = new ItemResponseShapeType();
43+
$request->ItemShape->BaseShape = DefaultShapeNamesType::ALL_PROPERTIES;
44+
$folder_id = new DistinguishedFolderIdType();
45+
$folder_id->Id = DistinguishedFolderIdNameType::CALENDAR;
46+
$request->ParentFolderIds->DistinguishedFolderId[] = $folder_id;
47+
$request->CalendarView = new CalendarViewType();
48+
$request->CalendarView->StartDate = $startDate->format('c');
49+
$request->CalendarView->EndDate = $endDate->format('c');
50+
$response = $this->client->FindItem($request);
51+
52+
// Iterate over the results, printing any error messages or event ids.
53+
$ResponseMessages = $response->ResponseMessages->FindItemResponseMessage;
54+
foreach ($ResponseMessages as $response_message)
55+
{
56+
if ($response_message->ResponseClass != ResponseClassType::SUCCESS)
57+
{
58+
$code = $response_message->ResponseCode;
59+
$message = $response_message->MessageText;
60+
fwrite(
61+
STDERR,
62+
"Failed to search for events with \"$code: $message\"\n"
63+
);
64+
continue;
65+
}
66+
67+
// Iterate over the events that were found, printing some data for each.
68+
$items = $response_message->RootFolder->Items->CalendarItem;
69+
foreach ($items as $item)
70+
{
71+
$id = $item->ItemId->Id;
72+
$start = new DateTime($item->Start);
73+
$end = new DateTime($item->End);
74+
$output = 'Found event ' . $item->ItemId->Id . "\n"
75+
. ' Change Key: ' . $item->ItemId->ChangeKey . "\n"
76+
. ' Title: ' . $item->Subject . "\n"
77+
. ' Start: ' . $start->format('l, F jS, Y g:ia') . "\n"
78+
. ' End: ' . $end->format('l, F jS, Y g:ia') . "\n\n";
79+
fwrite(STDOUT, $output);
80+
}
81+
}
82+
}
83+
}

src/Config.php

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Src;
4+
5+
class Config
6+
{
7+
/** @var string */
8+
private $email;
9+
10+
/** @var string */
11+
private $password;
12+
13+
/** @var array */
14+
private $team;
15+
16+
/** @var string */
17+
private $msExchangeHost;
18+
19+
/** @var string */
20+
private $msExchangeVersion;
21+
22+
public function __construct(array $config)
23+
{
24+
$this->email = $config['email'];
25+
$this->password = $config['plain_text_password'];
26+
$this->team = $config['team'];
27+
$this->msExchangeHost = $config['ms_exchange_host'];
28+
$this->msExchangeVersion = $config['ms_exchange_version'];
29+
}
30+
31+
public function getEmail(): string
32+
{
33+
return $this->email;
34+
}
35+
36+
public function getPassword(): string
37+
{
38+
return $this->password;
39+
}
40+
41+
public function getTeam(): array
42+
{
43+
return $this->team;
44+
}
45+
46+
public function getMsExchangeHost(): string
47+
{
48+
return $this->msExchangeHost;
49+
}
50+
51+
public function getMsExchangeVersion(): string
52+
{
53+
return $this->msExchangeVersion;
54+
}
55+
}

sync.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<?php declare(strict_types=1);
22

3-
use Symfony\Component\Dotenv\Dotenv;
3+
use Src\Config;
4+
use Src\MsExchange\Client;
45

56
require_once 'vendor/autoload.php';
67

7-
$dotenv = new Dotenv();
8-
$dotenv->load(__DIR__.'/.env');
9-
10-
8+
$config = new Config(require_once __DIR__ . '/app/config/config.php');
9+
$exchangeClient = new Client($config);
1110

11+
$exchangeClient->searchForVacationEvents();

0 commit comments

Comments
 (0)