Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
72 / 72
100.00% covered (success)
100.00%
14 / 14
CRAP
100.00% covered (success)
100.00%
1 / 1
MsProjectMPX
100.00% covered (success)
100.00%
72 / 72
100.00% covered (success)
100.00%
14 / 14
34
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 save
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 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
 writeRecordMPX
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeRecord30
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeRecord40
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeRecord41
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeRecord50
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeRecord60
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeRecord61
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeRecord70
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 writeRecord75
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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\Resource;
25use PhpOffice\PhpProject\Task;
26
27/**
28 * MsProjectMPx
29 *
30 * @category    PHPProject
31 * @package        PHPProject
32 * @copyright    Copyright (c) 2012 - 2012 PHPProject (https://github.com/PHPOffice/PHPProject)
33 */
34class MsProjectMPX implements WriterInterface
35{
36    /**
37     * PHPProject object
38     *
39     * @var \PhpOffice\PhpProject\PhpProject
40     */
41    private $phpProject;
42    
43    /**
44     * Content to write in File
45     * @var string[]
46     */
47    private $fileContent = array();
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    }
59    
60    /**
61     * 
62     * @param string $pFilename
63     * @throws \Exception
64     */
65    public function save(string $pFilename): void
66    {
67        $arrProjectInfo = $this->sanitizeProject();
68        
69        $this->writeRecordMPX();
70        // Project Header
71        $this->writeRecord30($arrProjectInfo);
72        // Text Resource Table Definition
73        $this->writeRecord40();
74        // Numeric Resource Table Definition
75        $this->writeRecord41();
76        // Resources
77        foreach ($this->phpProject->getAllResources() as $oResource) {
78            $this->writeRecord50($oResource);
79        }
80        // Text Task Table Definition
81        $this->writeRecord60();
82        // Numeric Task Table Definition
83        $this->writeRecord61();
84        // Tasks
85        foreach ($this->phpProject->getAllTasks() as $oTask) {
86            $this->writeRecord70($oTask);
87        }
88        
89        // Writing XML Object in file
90        // Open file
91        if (file_exists($pFilename) && !is_writable($pFilename)) {
92            throw new \Exception("Could not open file $pFilename for writing.");
93        }
94        $fileHandle = fopen($pFilename, 'wb+');
95        // Write Content
96        fwrite($fileHandle, implode(PHP_EOL, $this->fileContent));
97        // Close file
98        fclose($fileHandle);
99    }
100    
101    /**
102     * @return array
103     */
104    private function sanitizeProject(): array
105    {
106        // Info Project
107        $minDate = 0;
108        // Browse all tasks
109        $arrTasks = $this->phpProject->getAllTasks();
110        foreach ($arrTasks as $oTask) {
111            if ($oTask->getTaskCount() == 0) {
112                $this->sanitizeTask($oTask);
113            } else {
114                $this->sanitizeTaskParent($oTask);
115            }
116            $tStartDate = $oTask->getStartDate();
117            if ($minDate == 0 || $tStartDate < $minDate) {
118                $minDate = $tStartDate;
119            }
120        }
121        
122        return array(
123            'date_start' => (int)$minDate
124        );
125    }
126    
127    /**
128     * Permits to clean a task
129     * - If the duration is not filled, but the end date is, we calculate it.
130     * - If the end date is not filled, but the duration is, we calculate it.
131     * @param Task $oTask
132     */
133    private function sanitizeTask(Task $oTask): void
134    {
135        $pDuration = $oTask->getDuration();
136        $pEndDate = $oTask->getEndDate();
137        $pStartDate = $oTask->getStartDate();
138        
139        if (is_null($pDuration) && !is_null($pEndDate)) {
140            $iTimeDiff = $pEndDate - $pStartDate;
141            $iNumDays = $iTimeDiff / 60 / 60 / 24;
142            $oTask->setDuration($iNumDays + 1);
143        } elseif (!is_null($pDuration) && is_null($pEndDate)) {
144            $oTask->setEndDate($pStartDate + ((int) $pDuration * 24 * 60 * 60));
145        }
146    }
147    
148    /**
149     * Permits to clean parent task and calculate parent data like total duration,
150     *   date start and complete average.
151     * @param Task $oParentTask
152     */
153    private function sanitizeTaskParent(Task $oParentTask): void
154    {
155        $arrTasksChilds = $oParentTask->getTasks();
156        
157        $iProgress = 0;
158        $tStartDate = null;
159        $tEndDate = null;
160        foreach ($arrTasksChilds as $oTaskChild) {
161            if ($oTaskChild->getTaskCount() == 0) {
162                $this->sanitizeTask($oTaskChild);
163            } else {
164                $this->sanitizeTaskParent($oTaskChild);
165            }
166            
167            $iProgress += $oTaskChild->getProgress();
168            if (is_null($tStartDate)) {
169                $tStartDate = $oTaskChild->getStartDate();
170            } elseif ($tStartDate > $oTaskChild->getStartDate()) {
171                $tStartDate = $oTaskChild->getStartDate();
172            }
173            
174            if (is_null($tEndDate)) {
175                $tEndDate = $oTaskChild->getEndDate();
176            } elseif ($tEndDate < $oTaskChild->getEndDate()) {
177                $tEndDate = $oTaskChild->getEndDate();
178            }
179        }
180        $oParentTask->setProgress($iProgress / $oParentTask->getTaskCount());
181        $oParentTask->setStartDate($tStartDate);
182        $oParentTask->setEndDate($tEndDate);
183        $oParentTask->setDuration((($tEndDate - $tStartDate) / 60 / 60 / 24) + 1);
184    }
185
186    /**
187     * Record MPX
188     */
189    private function writeRecordMPX(): void
190    {
191        $this->fileContent[] = 'MPX;Microsoft Project for Windows;4.0;ANSI';
192    }
193    
194    /**
195     * Record "Project Header"
196     */
197    private function writeRecord30(array $arrProjectInfo): void
198    {
199        $this->fileContent[] = '30;Project1;;;Standard;'.date('d/m/Y', $arrProjectInfo['date_start']).';;0;'.date('d/m/Y').';;$0,00;$0,00;$0,00;0h;0h;0h;0%;0d;0d;0d;0%;;;;;0d;0d';
200    }
201    
202    /**
203     * Record "Text Resource Table Definition"
204     */
205    private function writeRecord40(): void
206    {
207        $this->fileContent[] = '40;ID;Name';
208    }
209    
210    /**
211     * Record "Numeric Resource Table Definition"
212     */
213    private function writeRecord41(): void
214    {
215        $this->fileContent[] = '41;40;1';
216    }
217
218    /**
219     * Record "Resource"
220     * @param Resource $oResource
221     */
222    private function writeRecord50(Resource $oResource): void
223    {
224        $this->fileContent[] = '50;'.$oResource->getIndex().';'.$oResource->getTitle();
225    }
226
227    /**
228     * Record "Text Task Table Definition"
229     */
230    private function writeRecord60(): void
231    {
232        $this->fileContent[] = '60;ID;Name;Duration;% Complete;Start';
233    }
234    
235    /**
236     * Record "Numeric Task Table Definition"
237     */
238    private function writeRecord61(): void
239    {
240        $this->fileContent[] = '61;90;1;40;44;50';
241    }
242
243    /**
244     * Record "Task"
245     * @param Task $oTask
246     */
247    private function writeRecord70(Task $oTask): void
248    {
249        $this->fileContent[] = '70;'.$oTask->getIndex().';'.$oTask->getName().';'.$oTask->getDuration().'d;'.number_format($oTask->getProgress() ?? 0, 1).';'.date('d/m/Y', $oTask->getStartDate() ?? time());
250        
251        foreach ($oTask->getResources() as $oResource) {
252            $this->writeRecord75($oResource);
253        }
254        
255        foreach ($oTask->getTasks() as $oSubTask) {
256            $this->writeRecord70($oSubTask);
257        }
258    }
259    
260    /**
261     * Record "Resource Assignment"
262     * @param Resource $oResource
263     */
264    private function writeRecord75(Resource $oResource): void
265    {
266        $this->fileContent[] = '75;'.$oResource->getIndex().';1;;;;;;;;;;;';
267    }
268}