Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
diff_files
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 5
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 check_requirements
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 run
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 1
380
 get_step_count
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_task_lang_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 *
4 * This file is part of the phpBB Forum Software package.
5 *
6 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7 * @license GNU General Public License, version 2 (GPL-2.0)
8 *
9 * For full copyright and license information, please see
10 * the docs/CREDITS.txt file.
11 *
12 */
13
14namespace phpbb\install\module\update_filesystem\task;
15
16use phpbb\install\exception\resource_limit_reached_exception;
17use phpbb\install\exception\user_interaction_required_exception;
18use phpbb\install\helper\config;
19use phpbb\install\helper\container_factory;
20use phpbb\install\helper\iohandler\iohandler_interface;
21use phpbb\install\helper\update_helper;
22use phpbb\install\task_base;
23
24/**
25 * Merges user made changes into the files
26 */
27class diff_files extends task_base
28{
29    /**
30     * @var \phpbb\cache\driver\driver_interface
31     */
32    protected $cache;
33
34    /**
35     * @var config
36     */
37    protected $installer_config;
38
39    /**
40     * @var iohandler_interface
41     */
42    protected $iohandler;
43
44    /**
45     * @var string
46     */
47    protected $phpbb_root_path;
48
49    /**
50     * @var string
51     */
52    protected $php_ext;
53
54    /**
55     * @var update_helper
56     */
57    protected $update_helper;
58
59    /**
60     * Constructor
61     *
62     * @param container_factory        $container
63     * @param config                $config
64     * @param iohandler_interface    $iohandler
65     * @param update_helper            $update_helper
66     * @param string                $phpbb_root_path
67     * @param string                $php_ext
68     */
69    public function __construct(container_factory $container, config $config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path, $php_ext)
70    {
71        $this->installer_config    = $config;
72        $this->iohandler        = $iohandler;
73        $this->update_helper    = $update_helper;
74        $this->phpbb_root_path    = $phpbb_root_path;
75        $this->php_ext            = $php_ext;
76
77        $this->cache            = $container->get('cache.driver');
78
79        parent::__construct(false);
80    }
81
82    /**
83     * {@inheritdoc}
84     */
85    public function check_requirements()
86    {
87        $files_to_diff = $this->installer_config->get('update_files', array());
88        $files_to_diff = (isset($files_to_diff['update_with_diff'])) ? $files_to_diff['update_with_diff'] : array();
89
90        return $this->installer_config->get('do_update_files', false) && count($files_to_diff) > 0;
91    }
92
93    /**
94     * {@inheritdoc}
95     */
96    public function run()
97    {
98        // Include diff engine
99        $this->update_helper->include_file('includes/diff/diff.' . $this->php_ext);
100        $this->update_helper->include_file('includes/diff/engine.' . $this->php_ext);
101
102        // Set up basic vars
103        $old_path = $this->update_helper->get_path_to_old_update_files();
104        $new_path = $this->update_helper->get_path_to_new_update_files();
105
106        $update_files = $this->installer_config->get('update_files', array());
107        $files_to_diff = $update_files['update_with_diff'];
108
109        // Set progress bar
110        $this->iohandler->set_task_count(count($files_to_diff), true);
111        $this->iohandler->set_progress('UPDATE_FILE_DIFF', 0);
112        $progress_count = $this->installer_config->get('file_diff_update_count', 0);
113
114        // Recover progress
115        $progress_key = $this->installer_config->get('differ_progress_key', -1);
116        $progress_recovered = ($progress_key === -1);
117        $merge_conflicts = $this->installer_config->get('merge_conflict_list', array());
118
119        foreach ($files_to_diff as $key => $filename)
120        {
121            if ($progress_recovered === false)
122            {
123                if ($progress_key === $key)
124                {
125                    $progress_recovered = true;
126                }
127
128                continue;
129            }
130
131            // Read in files' content
132            $file_contents = array();
133
134            // Handle the special case when user created a file with the filename that is now new in the core
135            if (file_exists($old_path . $filename))
136            {
137                $file_contents[0] = file_get_contents($old_path . $filename);
138
139                $filenames = array(
140                    $this->phpbb_root_path . $filename,
141                    $new_path . $filename
142                );
143
144                foreach ($filenames as $file_to_diff)
145                {
146                    $file_contents[] = file_get_contents($file_to_diff);
147
148                    if ($file_contents[count($file_contents) - 1] === false)
149                    {
150                        $this->iohandler->add_error_message(array('FILE_DIFFER_ERROR_FILE_CANNOT_BE_READ', $files_to_diff));
151                        unset($file_contents);
152                        throw new user_interaction_required_exception();
153                    }
154                }
155
156                $diff = new \diff3($file_contents[0], $file_contents[1], $file_contents[2]);
157
158                $file_is_merged = $diff->merged_output() === $file_contents[1];
159
160                // Handle conflicts
161                if ($diff->get_num_conflicts() !== 0)
162                {
163                    // Check if current file content is merge of new or original file
164                    $tmp = [
165                        'file1'        => $file_contents[1],
166                        'file2'        => implode("\n", $diff->merged_new_output()),
167                    ];
168
169                    $diff2 = new \diff($tmp['file1'], $tmp['file2']);
170                    $empty = $diff2->is_empty();
171
172                    if (!$empty)
173                    {
174                        unset($tmp, $diff2);
175
176                        // We check if the user merged with his output
177                        $tmp = [
178                            'file1'        => $file_contents[1],
179                            'file2'        => implode("\n", $diff->merged_orig_output()),
180                        ];
181
182                        $diff2 = new \diff($tmp['file1'], $tmp['file2']);
183                        $empty = $diff2->is_empty();
184                    }
185
186                    unset($diff2);
187
188                    if (!$empty && in_array($filename, $merge_conflicts))
189                    {
190                    $merge_conflicts[] = $filename;
191                }
192                    else
193                    {
194                        $file_is_merged = true;
195                    }
196                }
197
198                if (!$file_is_merged)
199                {
200                    // Save merged output
201                    $this->cache->put(
202                        '_file_' . md5($filename),
203                        base64_encode(implode("\n", $diff->merged_output()))
204                    );
205                }
206                else
207                {
208                    unset($update_files['update_with_diff'][$key]);
209                }
210
211                unset($file_contents);
212                unset($diff);
213            }
214            else
215            {
216                $new_file_content = file_get_contents($new_path . $filename);
217
218                if ($new_file_content === false)
219                {
220                    $this->iohandler->add_error_message(array('FILE_DIFFER_ERROR_FILE_CANNOT_BE_READ', $files_to_diff));
221                    unset($new_file_content );
222                    throw new user_interaction_required_exception();
223                }
224
225                // Save new file content to cache
226                $this->cache->put(
227                    '_file_' . md5($filename),
228                    base64_encode($new_file_content)
229                );
230                unset($new_file_content);
231            }
232
233            $progress_count++;
234            $this->iohandler->set_progress('UPDATE_FILE_DIFF', $progress_count);
235
236            if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0)
237            {
238                // Save differ progress
239                $this->installer_config->set('differ_progress_key', $key);
240                $this->installer_config->set('merge_conflict_list', $merge_conflicts);
241                $this->installer_config->set('file_diff_update_count', $progress_count);
242
243                foreach ($update_files as $type => $files)
244                {
245                    if (empty($files))
246                    {
247                        unset($update_files[$type]);
248                    }
249                }
250
251                $this->installer_config->set('update_files', $update_files);
252
253                // Request refresh
254                throw new resource_limit_reached_exception();
255            }
256        }
257
258        $this->iohandler->finish_progress('ALL_FILES_DIFFED');
259        $this->installer_config->set('merge_conflict_list', $merge_conflicts);
260        $this->installer_config->set('differ_progress_key', -1);
261
262        foreach ($update_files as $type => $files)
263        {
264            if (empty($files))
265            {
266                unset($update_files[$type]);
267            }
268        }
269
270        $this->installer_config->set('update_files', $update_files);
271    }
272
273    /**
274     * {@inheritdoc}
275     */
276    public static function get_step_count()
277    {
278        return 0;
279    }
280
281    /**
282     * {@inheritdoc}
283     */
284    public function get_task_lang_name()
285    {
286        return '';
287    }
288}