InteractiveEditor::setDocumentName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 0
cts 3
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * This file is part of the SVN-Buddy library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/svn-buddy
9
 */
10
11
namespace ConsoleHelpers\SVNBuddy;
12
13
14
class InteractiveEditor
15
{
16
17
	/**
18
	 * Document name.
19
	 *
20
	 * @var string
21
	 */
22
	private $_documentName = '';
23
24
	/**
25
	 * Content.
26
	 *
27
	 * @var string
28
	 */
29
	private $_content = '';
30
31
	/**
32
	 * Set the document name. Depending on the editor, this may be exposed to
33
	 * the user and can give them a sense of what they're editing.
34
	 *
35
	 * @param string $name Document name.
36
	 *
37
	 * @return self
38
	 */
39
	public function setDocumentName($name)
40
	{
41
		$this->_documentName = preg_replace('/[^A-Z0-9._-]+/i', '', $name);
42
43
		return $this;
44
	}
45
46
	/**
47
	 * Get the current document name. See @{method:setName} for details.
48
	 *
49
	 * @return string Current document name.
50
	 */
51
	public function getDocumentName()
52
	{
53
		if ( !strlen($this->_documentName) ) {
54
			return 'untitled';
55
		}
56
57
		return $this->_documentName;
58
	}
59
60
	/**
61
	 * Set the text content to be edited.
62
	 *
63
	 * @param string $content New content.
64
	 *
65
	 * @return self
66
	 */
67
	public function setContent($content)
68
	{
69
		$this->_content = $content;
70
71
		return $this;
72
	}
73
74
	/**
75
	 * Retrieve the current content.
76
	 *
77
	 * @return string
78
	 */
79
	public function getContent()
80
	{
81
		return $this->_content;
82
	}
83
84
	/**
85
	 * Launch an editor and edit the content. The edited content will be returned.
86
	 *
87
	 * @return string Edited content.
88
	 * @throws \RuntimeException When any of temporary file operation fails.
89
	 */
90
	public function launch()
91
	{
92
		$tmp_file = tempnam(sys_get_temp_dir(), $this->getDocumentName() . '_');
93
94
		if ( $tmp_file === false ) {
95
			throw new \RuntimeException('Unable to create temporary file.');
96
		}
97
98
		if ( file_put_contents($tmp_file, $this->getContent()) === false ) {
99
			throw new \RuntimeException('Unable to write content to temporary file.');
100
		}
101
102
		$exit_code = $this->_invokeEditor($this->_getEditor(), $tmp_file);
103
104
		if ( $exit_code ) {
105
			unlink($tmp_file);
106
			throw new \RuntimeException('Editor exited with an error code (#' . $exit_code . ').');
107
		}
108
109
		$new_content = file_get_contents($tmp_file);
110
111
		if ( $new_content === false ) {
112
			throw new \RuntimeException('Unable to read content from temporary file.');
113
		}
114
115
		unlink($tmp_file);
116
117
		$this->setContent($new_content);
118
119
		return $this->getContent();
120
	}
121
122
	/**
123
	 * Opens the editor.
124
	 *
125
	 * @param string $editor Editor.
126
	 * @param string $file   Path.
127
	 *
128
	 * @return integer
129
	 * @throws \RuntimeException When failed to open the editor.
130
	 */
131
	private function _invokeEditor($editor, $file)
132
	{
133
		$command = $editor . ' ' . escapeshellarg($file);
134
135
		$pipes = array();
136
		$spec = array(STDIN, STDOUT, STDERR);
137
138
		$proc = proc_open($command, $spec, $pipes);
139
140
		if ( !is_resource($proc) ) {
141
			throw new \RuntimeException('Failed to run: ' . $command);
142
		}
143
144
		return proc_close($proc);
145
	}
146
147
	/**
148
	 * Get the name of the editor program to use. The value of the environmental
149
	 * variable $EDITOR will be used if available; otherwise, the `editor` binary
150
	 * if present; otherwise the best editor will be selected.
151
	 *
152
	 * @return string Command-line editing program.
153
	 * @throws \LogicException When editor can't be found.
154
	 */
155
	private function _getEditor()
156
	{
157
		$editor = getenv('EDITOR');
158
159
		if ( $editor ) {
160
			return $editor;
161
		}
162
163
		// Look for `editor` in PATH, some systems provide an editor which is linked to something sensible.
164
		if ( $this->_fileExistsInPath('editor') ) {
165
			return 'editor';
166
		}
167
168
		if ( $this->_fileExistsInPath('nano') ) {
169
			return 'nano';
170
		}
171
172
		throw new \LogicException(
173
			'Unable to launch an interactive text editor. Set the EDITOR environment variable to an appropriate editor.'
174
		);
175
	}
176
177
	/**
178
	 * Determines if file exists in PATH.
179
	 *
180
	 * @param string $file File.
181
	 *
182
	 * @return boolean
183
	 */
184
	private function _fileExistsInPath($file)
185
	{
186
		$output = '';
187
		$exit_code = 0;
188
		exec('which ' . escapeshellarg($file) . ' 2>&1', $output, $exit_code);
0 ignored issues
show
Bug introduced by
$output of type string is incompatible with the type array expected by parameter $output of exec(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

188
		exec('which ' . escapeshellarg($file) . ' 2>&1', /** @scrutinizer ignore-type */ $output, $exit_code);
Loading history...
189
190
		return $exit_code == 0;
191
	}
192
193
}
194