Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
248 / 248
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
GanttProject
100.00% covered (success)
100.00%
248 / 248
100.00% covered (success)
100.00%
8 / 8
34
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 save
100.00% covered (success)
100.00%
171 / 171
100.00% covered (success)
100.00%
1 / 1
9
 writeTask
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
5
 writeResource
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 writeAllocation
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 sanitizeProject
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 sanitizeTask
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 sanitizeTaskParent
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3/**
4 * This file is part of PHPProject - A pure PHP library for reading and writing
5 * presentations documents.
6 *
7 * PHPProject is free software distributed under the terms of the GNU Lesser
8 * General Public License version 3 as published by the Free Software Foundation.
9 *
10 * For the full copyright and license information, please read the LICENSE
11 * file that was distributed with this source code. For the full list of
12 * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
13 *
14 * @link        https://github.com/PHPOffice/PHPProject
15 * @copyright   2009-2014 PHPProject contributors
16 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17 */
18
19declare(strict_types=1);
20
21namespace PhpOffice\PhpProject\Writer;
22
23use PhpOffice\PhpProject\PhpProject;
24use PhpOffice\PhpProject\Shared\XMLWriter;
25use PhpOffice\PhpProject\Task;
26
27/**
28 * PHPProject_Writer_GanttProject
29 *
30 * @category    PHPProject
31 * @package        PHPProject
32 * @copyright    Copyright (c) 2012 - 2012 PHPProject (https://github.com/PHPOffice/PHPProject)
33 */
34class GanttProject implements WriterInterface
35{
36    /**
37     * PHPProject object
38     *
39     * @var \PhpOffice\PhpProject\PhpProject
40     */
41    private $phpProject;
42    
43    /**
44     * 
45     * @var array
46     */
47    private $arrAllocations;
48    
49    
50    /**
51     * Create a new PHPProject_Writer_GanttProject
52     *
53     * @param    PHPProject    $phpProject
54     */
55    public function __construct(PhpProject $phpProject)
56    {
57        $this->phpProject = $phpProject;
58        $this->arrAllocations = array();
59    }
60    
61    /**
62     * 
63     * @param string $pFilename
64     * @throws \Exception
65     */
66    public function save(string $pFilename): void
67    {
68        $arrProjectInfo = $this->sanitizeProject();
69        
70        // Create XML Object
71        $oXML = new XMLWriter(XMLWriter::STORAGE_DISK);
72        $oXML->startDocument('1.0', 'UTF-8');
73        // project
74        $oXML->startElement('project');
75        if (isset($arrProjectInfo['date_start']) && $arrProjectInfo['date_start'] != 0) {
76            $oXML->writeAttribute('view-date', date('Y-m-d', $arrProjectInfo['date_start']));
77        }
78        $oXML->writeAttribute('version', '2.0');
79        
80        // view
81        $oXML->startElement('view');
82        $oXML->writeAttribute('id', 'resource-table');
83        
84        // field
85        $oXML->startElement('field');
86        $oXML->writeAttribute('id', '0');
87        $oXML->writeAttribute('name', 'Nom');
88        $oXML->writeAttribute('width', '56');
89        $oXML->writeAttribute('valuetype', '0');
90        $oXML->endElement();
91        
92        $oXML->startElement('field');
93        $oXML->writeAttribute('id', '1');
94        $oXML->writeAttribute('name', 'Rôle par défaut');
95        $oXML->writeAttribute('width', '43');
96        $oXML->writeAttribute('valuetype', '1');
97        $oXML->endElement();
98        
99        // >view
100        $oXML->endElement();
101        
102        // tasks
103        $oXML->startElement('tasks');
104        
105        // tasksproperties
106        $oXML->startElement('tasksproperties');
107        
108        // taskproperty
109        $oXML->startElement('taskproperty');
110        $oXML->writeAttribute('id', 'tpd0');
111        $oXML->writeAttribute('name', 'type');
112        $oXML->writeAttribute('type', 'default');
113        $oXML->writeAttribute('valuetype', 'icon');
114        $oXML->endElement();
115        
116        $oXML->startElement('taskproperty');
117        $oXML->writeAttribute('id', 'tpd1');
118        $oXML->writeAttribute('name', 'priority');
119        $oXML->writeAttribute('type', 'default');
120        $oXML->writeAttribute('valuetype', 'icon');
121        $oXML->endElement();
122        
123        $oXML->startElement('taskproperty');
124        $oXML->writeAttribute('id', 'tpd2');
125        $oXML->writeAttribute('name', 'info');
126        $oXML->writeAttribute('type', 'default');
127        $oXML->writeAttribute('valuetype', 'icon');
128        $oXML->endElement();
129        
130        $oXML->startElement('taskproperty');
131        $oXML->writeAttribute('id', 'tpd3');
132        $oXML->writeAttribute('name', 'name');
133        $oXML->writeAttribute('type', 'default');
134        $oXML->writeAttribute('valuetype', 'text');
135        $oXML->endElement();
136        
137        $oXML->startElement('taskproperty');
138        $oXML->writeAttribute('id', 'tpd4');
139        $oXML->writeAttribute('name', 'begindate');
140        $oXML->writeAttribute('type', 'default');
141        $oXML->writeAttribute('valuetype', 'date');
142        $oXML->endElement();
143        
144        $oXML->startElement('taskproperty');
145        $oXML->writeAttribute('id', 'tpd5');
146        $oXML->writeAttribute('name', 'enddate');
147        $oXML->writeAttribute('type', 'default');
148        $oXML->writeAttribute('valuetype', 'date');
149        $oXML->endElement();
150        
151        $oXML->startElement('taskproperty');
152        $oXML->writeAttribute('id', 'tpd6');
153        $oXML->writeAttribute('name', 'duration');
154        $oXML->writeAttribute('type', 'default');
155        $oXML->writeAttribute('valuetype', 'int');
156        $oXML->endElement();
157        
158        $oXML->startElement('taskproperty');
159        $oXML->writeAttribute('id', 'tpd7');
160        $oXML->writeAttribute('name', 'completion');
161        $oXML->writeAttribute('type', 'default');
162        $oXML->writeAttribute('valuetype', 'int');
163        $oXML->endElement();
164        
165        $oXML->startElement('taskproperty');
166        $oXML->writeAttribute('id', 'tpd8');
167        $oXML->writeAttribute('name', 'coordinator');
168        $oXML->writeAttribute('type', 'default');
169        $oXML->writeAttribute('valuetype', 'text');
170        $oXML->endElement();
171        
172        $oXML->startElement('taskproperty');
173        $oXML->writeAttribute('id', 'tpd9');
174        $oXML->writeAttribute('name', 'predecessorsr');
175        $oXML->writeAttribute('type', 'default');
176        $oXML->writeAttribute('valuetype', 'text');
177        $oXML->endElement();
178        // >taskproperty
179        
180        // >tasksproperties
181        $oXML->endElement();
182        
183        // task
184        foreach ($this->phpProject->getAllTasks() as $oTask) {
185            $this->writeTask($oXML, $oTask);
186        }
187        
188        // >tasks
189        $oXML->endElement();
190        
191        // resources
192        $oXML->startElement('resources');
193        
194        // resource
195        foreach ($this->phpProject->getAllResources() as $oResource) {
196            $this->writeResource($oXML, $oResource);
197        }
198        
199        // >resources
200        $oXML->endElement();
201        
202        // allocations
203        $oXML->startElement('allocations');
204        
205        if (count($this->arrAllocations) > 0) {
206            foreach ($this->arrAllocations as $itmAllocation) {
207                $this->writeAllocation($oXML, $itmAllocation['id_task'], $itmAllocation['id_res']);
208            }
209        }
210        
211        // >allocations
212        $oXML->endElement();
213        
214        // taskdisplaycolumns
215        $oXML->startElement('tasksproperties');
216        
217        // displaycolumn
218        $oXML->startElement('displaycolumn');
219        $oXML->writeAttribute('property-id', 'tpd2');
220        $oXML->writeAttribute('order', '-1');
221        $oXML->writeAttribute('width', '75');
222        $oXML->writeAttribute('visible', 'false');
223        $oXML->endElement();
224        
225        $oXML->startElement('displaycolumn');
226        $oXML->writeAttribute('property-id', 'tpd7');
227        $oXML->writeAttribute('order', '-1');
228        $oXML->writeAttribute('width', '75');
229        $oXML->writeAttribute('visible', 'false');
230        $oXML->endElement();
231        
232        $oXML->startElement('displaycolumn');
233        $oXML->writeAttribute('property-id', 'tpd6');
234        $oXML->writeAttribute('order', '-1');
235        $oXML->writeAttribute('width', '75');
236        $oXML->writeAttribute('visible', 'false');
237        $oXML->endElement();
238        
239        $oXML->startElement('displaycolumn');
240        $oXML->writeAttribute('property-id', 'tpd10');
241        $oXML->writeAttribute('order', '-1');
242        $oXML->writeAttribute('width', '75');
243        $oXML->writeAttribute('visible', 'false');
244        $oXML->endElement();
245        
246        $oXML->startElement('displaycolumn');
247        $oXML->writeAttribute('property-id', 'tpd1');
248        $oXML->writeAttribute('order', '-1');
249        $oXML->writeAttribute('width', '75');
250        $oXML->writeAttribute('visible', 'false');
251        $oXML->endElement();
252        
253        $oXML->startElement('displaycolumn');
254        $oXML->writeAttribute('property-id', 'tpd9');
255        $oXML->writeAttribute('order', '-1');
256        $oXML->writeAttribute('width', '75');
257        $oXML->writeAttribute('visible', 'false');
258        $oXML->endElement();
259        
260        $oXML->startElement('displaycolumn');
261        $oXML->writeAttribute('property-id', 'tpd8');
262        $oXML->writeAttribute('order', '-1');
263        $oXML->writeAttribute('width', '75');
264        $oXML->writeAttribute('visible', 'false');
265        $oXML->endElement();
266        
267        $oXML->startElement('displaycolumn');
268        $oXML->writeAttribute('property-id', 'tpd0');
269        $oXML->writeAttribute('order', '-1');
270        $oXML->writeAttribute('width', '75');
271        $oXML->writeAttribute('visible', 'false');
272        $oXML->endElement();
273        
274        $oXML->startElement('displaycolumn');
275        $oXML->writeAttribute('property-id', 'tpd3');
276        $oXML->writeAttribute('order', '0');
277        $oXML->writeAttribute('width', '199');
278        $oXML->writeAttribute('visible', 'true');
279        $oXML->endElement();
280        
281        $oXML->startElement('displaycolumn');
282        $oXML->writeAttribute('property-id', 'tpd4');
283        $oXML->writeAttribute('order', '1');
284        $oXML->writeAttribute('width', '75');
285        $oXML->writeAttribute('visible', 'true');
286        $oXML->endElement();
287        
288        $oXML->startElement('displaycolumn');
289        $oXML->writeAttribute('property-id', 'tpd5');
290        $oXML->writeAttribute('order', '2');
291        $oXML->writeAttribute('width', '75');
292        $oXML->writeAttribute('visible', 'true');
293        $oXML->endElement();
294        // >displaycolumn
295        
296        // >taskdisplaycolumns
297        $oXML->endElement();
298        
299        // >project
300        $oXML->endElement();
301        
302        // Writing XML Object in file
303        // Open file
304        if (file_exists($pFilename) && !is_writable($pFilename)) {
305            throw new \Exception("Could not open file $pFilename for writing.");
306        }
307        $fileHandle = fopen($pFilename, 'wb+');
308        // Write XML Content
309        fwrite($fileHandle, $oXML->getData());
310        // Close file
311        fclose($fileHandle);
312    }
313    
314    private function writeTask(XMLWriter $oXML, Task $oTask): void
315    {
316        $oXML->startElement('task');
317        $oXML->writeAttribute('id', $oTask->getIndex());
318        $oXML->writeAttribute('name', $oTask->getName());
319        $oXML->writeAttribute('start', date('Y-m-d', $oTask->getStartDate() ?? time()));
320        $oXML->writeAttribute('duration', $oTask->getDuration());
321        $oXML->writeAttribute('complete', $oTask->getProgress() * 100);
322        $oXML->writeAttribute('meeting', 'false');
323        $oXML->writeAttribute('expand', 'true');
324        
325        // Resources Allocations
326        if ($oTask->getResourceCount() > 0) {
327            foreach ($oTask->getResources() as $oResource) {
328                $itmAllocation = array();
329                $itmAllocation['id_res'] = $oResource->getIndex();
330                $itmAllocation['id_task'] = $oTask->getIndex();
331                $this->arrAllocations[] = $itmAllocation;
332            }
333        }
334        
335        // Children
336        if ($oTask->getTaskCount() > 0) {
337            $arrTasksChilds = $oTask->getTasks();
338            foreach ($arrTasksChilds as $oTaskChild) {
339                $this->writeTask($oXML, $oTaskChild);
340            }
341        } else {
342            // Nothing
343        }
344        $oXML->endElement();
345    }
346    
347    /**
348     * 
349     * @param XMLWriter $oXML
350     * @param \PhpOffice\PhpProject\Resource $oResource
351     */
352    private function writeResource(XMLWriter $oXML, \PhpOffice\PhpProject\Resource $oResource): void
353    {
354        $oXML->startElement('resource');
355        $oXML->writeAttribute('id', $oResource->getIndex());
356        $oXML->writeAttribute('name', $oResource->getTitle());
357        $oXML->writeAttribute('function', 'Default:0');
358        $oXML->writeAttribute('contacts', '');
359        $oXML->writeAttribute('phone', '');
360        $oXML->endElement();
361    }
362    
363    /**
364     * Write allocation of a resource for a task
365     * @param XMLWriter $oXML
366     * @param integer $piIdTask
367     * @param integer $piIdResource
368     */
369    private function writeAllocation(XMLWriter $oXML, int $piIdTask, int $piIdResource): void
370    {
371        $oXML->startElement('allocation');
372        $oXML->writeAttribute('task-id', $piIdTask);
373        $oXML->writeAttribute('resource-id', $piIdResource);
374        $oXML->writeAttribute('function', 'Default:0');
375        $oXML->writeAttribute('responsible', 'true');
376        $oXML->writeAttribute('load', '100.0');
377        $oXML->endElement();
378    }
379    
380    /**
381     * @return array
382     */
383    private function sanitizeProject(): array
384    {
385        // Info Project
386        $minDate = 0;
387        // Browse all tasks
388        $arrTasks = $this->phpProject->getAllTasks();
389        foreach ($arrTasks as $oTask) {
390            if ($oTask->getTaskCount() == 0) {
391                $this->sanitizeTask($oTask);
392            } else {
393                $this->sanitizeTaskParent($oTask);
394            }
395            $tStartDate = $oTask->getStartDate();
396            if ($minDate == 0 || $tStartDate < $minDate) {
397                $minDate = $tStartDate;
398            }
399        }
400        
401        return array(
402            'date_start' => (int)$minDate
403        );
404    }
405    
406    /**
407     * Permits to clean a task
408     * - If the duration is not filled, but the end date is, we calculate it.
409     * - If the end date is not filled, but the duration is, we calculate it.
410     * @param Task $oTask
411     */
412    private function sanitizeTask(Task $oTask): void
413    {
414        $pDuration = $oTask->getDuration();
415        $pEndDate = $oTask->getEndDate();
416        $pStartDate = $oTask->getStartDate();
417        
418        if (is_null($pDuration) && !is_null($pEndDate)) {
419            $iTimeDiff = $pEndDate - $pStartDate;
420            $iNumDays = $iTimeDiff / 60 / 60 / 24;
421            $oTask->setDuration($iNumDays + 1);
422        } elseif (!is_null($pDuration) && is_null($pEndDate)) {
423            $oTask->setEndDate($pStartDate + ((int) $pDuration * 24 * 60 * 60));
424        }
425    }
426    
427    /**
428     * Permits to clean parent task and calculate parent data like total duration,
429     *   date start and complete average.
430     * @param Task $oParentTask
431     */
432    private function sanitizeTaskParent(Task $oParentTask): void
433    {
434        $arrTasksChilds = $oParentTask->getTasks();
435        
436        $iProgress = 0;
437        $tStartDate = null;
438        $tEndDate = null;
439        foreach ($arrTasksChilds as $oTaskChild) {
440            if ($oTaskChild->getTaskCount() == 0) {
441                $this->sanitizeTask($oTaskChild);
442            } else {
443                $this->sanitizeTaskParent($oTaskChild);
444            }
445            
446            $iProgress += $oTaskChild->getProgress();
447            if (is_null($tStartDate)) {
448                $tStartDate = $oTaskChild->getStartDate();
449            } elseif ($tStartDate > $oTaskChild->getStartDate()) {
450                $tStartDate = $oTaskChild->getStartDate();
451            }
452            
453            if (is_null($tEndDate)) {
454                $tEndDate = $oTaskChild->getEndDate();
455            } elseif ($tEndDate < $oTaskChild->getEndDate()) {
456                $tEndDate = $oTaskChild->getEndDate();
457            }
458        }
459        $oParentTask->setProgress($iProgress / $oParentTask->getTaskCount());
460        $oParentTask->setStartDate($tStartDate);
461        $oParentTask->setEndDate($tEndDate);
462        $oParentTask->setDuration((($tEndDate - $tStartDate) / 60 / 60 / 24) + 1);
463    }
464}