Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.69% covered (success)
97.69%
127 / 130
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
MsProjectMPX
97.69% covered (success)
97.69%
127 / 130
88.89% covered (warning)
88.89%
8 / 9
73
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canRead
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 load
92.86% covered (success)
92.86%
39 / 42
0.00% covered (danger)
0.00%
0 / 1
23.19
 readRecord30
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 readRecord41
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 readRecord50
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 readRecord61
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
16
 readRecord70
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
10
 readRecord75
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
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\Reader;
22
23use PhpOffice\PhpProject\PhpProject;
24use PhpOffice\PhpProject\Task;
25
26/**
27 * MPX File Format
28 * @link http://support.microsoft.com/kb/270139/en-us
29 * @category    PHPProject
30 * @package        PHPProject
31 * @copyright    Copyright (c) 2012 - 2012 PHPProject (https://github.com/PHPOffice/PHPProject)
32 */
33class MsProjectMPX implements ReaderInterface
34{
35    /**
36     * PHPProject object
37     *
38     * @var \PhpOffice\PhpProject\PhpProject
39     */
40    private $phpProject;
41    
42    /**
43     * Numeric Resource Table
44     * @var string[]
45     */
46    private $defResource = array();
47    
48    /**
49     * Numeric Table Table
50     * @var string[]
51     */
52    private $defTask = array();
53    
54    /**
55     * Index in $defTask for the precedessor
56     * @var int|null
57     */
58    private $iParentTaskIdx;
59    
60    /**
61     * last Task created
62     * @var Task
63     */
64    private $oPreviousTask;
65    
66    /**
67     * Create a new GanttProject
68     */
69    public function __construct()
70    {
71        $this->phpProject = new PhpProject();
72    }
73    /**
74     *
75     * @param string $pFilename
76     * @return bool
77     */
78    public function canRead(string $pFilename): bool
79    {
80        if (!file_exists($pFilename) || !is_readable($pFilename)) {
81            return false;
82        }
83        $sContent = file_get_contents($pFilename);
84        $arrayLines = explode(PHP_EOL, $sContent);
85        
86        // The only required record is the File Creation record
87        foreach ($arrayLines as $sLine) {
88            $arrayRecord = explode(';', $sLine);
89            if (!is_numeric($arrayRecord[0]) && $arrayRecord[0] == 'MPX') {
90                return true;
91            }
92        }
93        
94        return false;
95    }
96    
97    /**
98     * 
99     * @param string $pFilename
100     * @throws \Exception
101     * @return PhpProject
102     */
103    public function load(string $pFilename): PhpProject
104    {
105        if (!$this->canRead($pFilename)) {
106            throw new \Exception('The file is not accessible.');
107        }
108        $sContent = file_get_contents($pFilename);
109        $arrayLines = explode(PHP_EOL, $sContent);
110        
111        foreach ($arrayLines as $sLine) {
112            $arrayRecord = explode(';', $sLine);
113            switch ($arrayRecord[0]) {
114                case 'MPX': // File Creation
115                case '10': // Currency Settings
116                case '11': // Default Settings
117                case '12': // Date and Time Settings
118                case '20': // Base Calendar Definition
119                case '25': // Base Calendar Hours
120                case '26': // Base Calendar Exception
121                    break;
122                case '30': // Project Header
123                    $this->readRecord30($arrayRecord);
124                    break;
125                case '40': // Text Resource Table Definition
126                    // Label for 41
127                    break;
128                case '41': // Numeric Resource Table Definition
129                    $this->readRecord41($arrayRecord);
130                    break;
131                case '50': // Resource
132                    $this->readRecord50($arrayRecord);
133                    break;
134                //case '51': // Resource Notes
135                //case '55': // Resource Calendar Definition
136                //case '56': // Resource Calendar Hours
137                //case '57': // Resource Calendar Exception
138                //    break;
139                case '60': // Text Task Table Definition
140                    // Label for 61
141                    break;
142                case '61': // Numeric Task Table Definition
143                    $this->readRecord61($arrayRecord);
144                    break;
145                case '70': // Task
146                    $this->readRecord70($arrayRecord);
147                    break;
148                //case '71': // Task Notes
149                //case '72': // Recurring Task
150                //    break;
151                case '75': // Resource Assignment
152                    $this->readRecord75($arrayRecord);
153                    break;
154                case '76': // Resource Assignment Workgroup Fields
155                case '80': // Project Names
156                case '81': // DDE and OLE Client Links
157                case '0': // Comments
158                default:
159                    // throw new \Exception('load : Not implemented ('.$arrayRecord[0].')');
160            }
161        }
162        
163        return $this->phpProject;
164    }
165    
166    /**
167     * Project Header
168     * @param array $record
169     */
170    private function readRecord30(array $record): void
171    {
172        // 0 : Record
173        // 1 : Project tab
174        // 2 : Company
175        // 3 : Manager
176        // 4 : Calendar (Standard used if no entry)
177        // 5 : Start Date (either this field or the next field is calculated for an imported file, depending on the Schedule From setting)
178        if (isset($record[5]) && !empty($record[5])) {
179            $this->phpProject->getInformations()->setStartDate($record[5]);
180        }
181        // 6 : Finish Date
182        //if (isset($record[6]) && !empty($record[6])) {
183        //    $this->phpProject->getInformations()->setEndDate($record[6]);
184        //}
185        // 7 : Schedule From (0 = start, 1 = finish)
186        // 8 : Current Date*
187        // 9 : Comments
188        // 10 : Cost*
189        // 11 : Baseline Cost
190        // 12 : Actual Cost
191        // 13 : Work
192        // 14 : Baseline Work
193        // 15 : Actual Work
194        // 16 : Work
195        // 17 : Duration
196        // 18 : Baseline Duration
197        // 19 : Actual Duration
198        // 20 : Percent Complete
199        // 21 : Baseline Start
200        // 22 : Baseline Finish
201        // 23 : Actual Start
202        // 24 : Actual Finish
203        // 25 : Start Variance
204        // 26 : Finish Variance
205        // 27 : Subject
206        // 28 : Author
207        // 29 : Keywords
208    }
209    
210    /**
211     * Numeric Resource Table Definition
212     * @param array $record
213     */
214    private function readRecord41(array $record): void
215    {
216        array_shift($record);
217        foreach ($record as $key => $item) {
218            switch ($item) {
219                case 1: // Name
220                    $this->defResource[$key+1] = 'setTitle';
221                    break;
222                case 40: // ID
223                    $this->defResource[$key+1] = 'setIndex';
224                    break;
225                case 41: // Max Units
226                    break;
227                case 49: // Unique ID
228                    break;
229                //default:
230                    //throw new \Exception('readRecord41 : Not implemented ('.$item.')');
231            }
232        }
233    }
234    
235    /**
236     * Resource
237     * @param array $record
238     */
239    private function readRecord50(array $record): void
240    {
241        $oResource = $this->phpProject->createResource();
242        
243        foreach ($this->defResource as $key => $method) {
244            $oResource->{$method}($record[$key]);
245        }
246    }
247    
248    /**
249     * Numeric Task Table Definition
250     * @param array $record
251     */
252    private function readRecord61(array $record): void
253    {
254        array_shift($record);
255        foreach ($record as $key => $item) {
256            switch ($item) {
257                case 1: // Name
258                    $this->defTask[$key + 1] = 'setName';
259                    break;
260                case 2: // WBS
261                    break;
262                case 3: // Outline Level
263                    break;
264                case 40: // Duration
265                    $this->defTask[$key + 1] = 'setDuration';
266                    break;
267                case 44: // % Complete
268                    $this->defTask[$key + 1] = 'setProgress';
269                    break;
270                case 50: // Start
271                    $this->defTask[$key + 1] = 'setStartDate';
272                    break;
273                case 58: // Actual Start
274                    break;
275                case 70: // Predecessors
276                    $this->iParentTaskIdx = $key + 1;
277                    break;
278                case 80: // Fixed
279                    break;
280                case 90: // ID
281                    $this->defTask[$key + 1] = 'setIndex';
282                    break;
283                case 91: // Constraint Type
284                    break;
285                case 98: // Unique ID
286                    break;
287                case 99: // Outline Number
288                    break;
289                case 120: // Summary
290                    break;
291                //default:
292                    //throw new \Exception('readRecord41 : Not implemented ('.$item.')');
293            }
294        }
295    }
296    
297    /**
298     * Task
299     * @param array $record
300     */
301    private function readRecord70(array $record): void
302    {
303        $oTask = null;
304        if (!is_null($this->iParentTaskIdx) && !empty($record[$this->iParentTaskIdx])) {
305            $oTaskParent = $this->phpProject->getTaskFromIndex($record[$this->iParentTaskIdx]);
306            if (is_object($oTaskParent)) {
307                $oTask = $oTaskParent->createTask();
308            }
309        }
310        if (is_null($oTask)) {
311            $oTask = $this->phpProject->createTask();
312        }
313        
314        foreach ($this->defTask as $key => $method) {
315            if ($method == 'setDuration') {
316                if (substr($record[$key], -1) == 'd') {
317                    $record[$key] = intval(substr($record[$key], 0, -1));
318                }
319            }
320            if ($method == 'setProgress') {
321                if (substr($record[$key], -1) == '%') {
322                    $record[$key] = substr($record[$key], 0, -1);
323                    $record[$key] = str_replace(',', '.', $record[$key]);
324                    $record[$key] = floatval($record[$key]) / 100;
325                }
326            }
327            $oTask->{$method}($record[$key]);
328        }
329        $this->oPreviousTask = $oTask;
330    }
331    
332    /**
333     * Resource Assignment
334     * @param array $record
335     */
336    private function readRecord75(array $record): void
337    {
338        // 0 : Record
339        // 1 : ID
340        $idResource = null;
341        if (isset($record[1]) && !empty($record[1])) {
342            $idResource = $record[1];
343        }
344        // 2 : Units
345        // 3 : Work
346        // 4 : Planned Work
347        // 5 : Actual Work
348        // 6 : Overtime Work
349        // 7 : Cost
350        // 8 : Planned Cost
351        // 9 : Actual Cost
352        // 10 : Start*
353        // 11 : Finish*
354        // 12 : Delay
355        // 13 : Resource Unique ID
356        
357        if (!is_null($idResource) && $this->oPreviousTask instanceof Task) {
358            $oResource = $this->phpProject->getResourceFromIndex($idResource);
359            if (!is_null($oResource)) {
360                $this->oPreviousTask->addResource($oResource);
361            }
362        }
363    }
364}