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\extension;
15
16/**
17* The extension metadata manager validates and gets meta-data for extensions
18*/
19class metadata_manager
20{
21	/**
22	* Name (including vendor) of the extension
23	* @var string
24	*/
25	protected $ext_name;
26
27	/**
28	* Metadata from the composer.json file
29	* @var array
30	*/
31	protected $metadata;
32
33	/**
34	* Link (including root path) to the metadata file
35	* @var string
36	*/
37	protected $metadata_file;
38
39	/**
40	* Creates the metadata manager
41	*
42	* @param string				$ext_name			Name (including vendor) of the extension
43	* @param string				$ext_path			Path to the extension directory including root path
44	*/
45	public function __construct($ext_name, $ext_path)
46	{
47		$this->ext_name = $ext_name;
48		$this->metadata = array();
49		$this->metadata_file = $ext_path . 'composer.json';
50	}
51
52	/**
53	* Processes and gets the metadata requested
54	*
55	* @param  string $element			All for all metadata that it has and is valid, otherwise specify which section you want by its shorthand term.
56	* @return array					Contains all of the requested metadata, throws an exception on failure
57	*/
58	public function get_metadata($element = 'all')
59	{
60		// Fetch and clean the metadata if not done yet
61		if ($this->metadata === array())
62		{
63			$this->fetch_metadata_from_file();
64		}
65
66		switch ($element)
67		{
68			case 'all':
69			default:
70				$this->validate();
71				return $this->metadata;
72			break;
73
74			case 'version':
75			case 'name':
76				$this->validate($element);
77				return $this->metadata[$element];
78			break;
79
80			case 'display-name':
81				return (isset($this->metadata['extra']['display-name'])) ? $this->metadata['extra']['display-name'] : $this->get_metadata('name');
82			break;
83		}
84	}
85
86	/**
87	* Gets the metadata file contents and cleans loaded file
88	*
89	* @throws \phpbb\extension\exception
90	*/
91	private function fetch_metadata_from_file()
92	{
93		if (!file_exists($this->metadata_file))
94		{
95			throw new \phpbb\extension\exception('FILE_NOT_FOUND', array($this->metadata_file));
96		}
97
98		if (!($file_contents = file_get_contents($this->metadata_file)))
99		{
100			throw new \phpbb\extension\exception('FILE_CONTENT_ERR', array($this->metadata_file));
101		}
102
103		if (($metadata = json_decode($file_contents, true)) === null)
104		{
105			throw new \phpbb\extension\exception('FILE_JSON_DECODE_ERR', array($this->metadata_file));
106		}
107
108		array_walk_recursive($metadata, array($this, 'sanitize_json'));
109		$this->metadata = $metadata;
110	}
111
112	/**
113	 * Sanitize input from JSON array using htmlspecialchars()
114	 *
115	 * @param mixed		$value	Value of array row
116	 * @param string	$key	Key of array row
117	 */
118	public function sanitize_json(&$value, $key)
119	{
120		$value = htmlspecialchars($value, ENT_COMPAT);
121	}
122
123	/**
124	* Validate fields
125	*
126	* @param string $name  ("all" for display and enable validation
127	* 						"display" for name, type, and authors
128	* 						"name", "type")
129	* @return Bool True if valid, throws an exception if invalid
130	* @throws \phpbb\extension\exception
131	*/
132	public function validate($name = 'display')
133	{
134		// Basic fields
135		$fields = array(
136			'name'		=> '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#',
137			'type'		=> '#^phpbb-extension$#',
138			'license'	=> '#.+#',
139			'version'	=> '#.+#',
140		);
141
142		switch ($name)
143		{
144			case 'all':
145				$this->validate_enable();
146			// no break
147
148			case 'display':
149				foreach ($fields as $field => $data)
150				{
151					$this->validate($field);
152				}
153
154				$this->validate_authors();
155			break;
156
157			default:
158				if (isset($fields[$name]))
159				{
160					if (!isset($this->metadata[$name]))
161					{
162						throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array($name));
163					}
164
165					if (!preg_match($fields[$name], $this->metadata[$name]))
166					{
167						throw new \phpbb\extension\exception('META_FIELD_INVALID', array($name));
168					}
169				}
170			break;
171		}
172
173		return true;
174	}
175
176	/**
177	* Validates the contents of the authors field
178	*
179	* @return boolean True when passes validation, throws exception if invalid
180	* @throws \phpbb\extension\exception
181	*/
182	public function validate_authors()
183	{
184		if (empty($this->metadata['authors']))
185		{
186			throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('authors'));
187		}
188
189		foreach ($this->metadata['authors'] as $author)
190		{
191			if (!isset($author['name']))
192			{
193				throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('author name'));
194			}
195		}
196
197		return true;
198	}
199
200	/**
201	* This array handles the verification that this extension can be enabled on this board
202	*
203	* @return bool True if validation succeeded, throws an exception if invalid
204	* @throws \phpbb\extension\exception
205	*/
206	public function validate_enable()
207	{
208		// Check for valid directory & phpBB, PHP versions
209		return $this->validate_dir() && $this->validate_require_phpbb() && $this->validate_require_php();
210	}
211
212	/**
213	* Validates the most basic directory structure to ensure it follows <vendor>/<ext> convention.
214	*
215	* @return boolean True when passes validation, throws an exception if invalid
216	* @throws \phpbb\extension\exception
217	*/
218	public function validate_dir()
219	{
220		if (substr_count($this->ext_name, '/') !== 1 || $this->ext_name != $this->get_metadata('name'))
221		{
222			throw new \phpbb\extension\exception('EXTENSION_DIR_INVALID');
223		}
224
225		return true;
226	}
227
228
229	/**
230	* Validates the contents of the phpbb requirement field
231	*
232	* @return boolean True when passes validation, throws an exception if invalid
233	* @throws \phpbb\extension\exception
234	*/
235	public function validate_require_phpbb()
236	{
237		if (!isset($this->metadata['extra']['soft-require']['phpbb/phpbb']))
238		{
239			throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('soft-require'));
240		}
241
242		return true;
243	}
244
245	/**
246	* Validates the contents of the php requirement field
247	*
248	* @return boolean True when passes validation, throws an exception if invalid
249	* @throws \phpbb\extension\exception
250	*/
251	public function validate_require_php()
252	{
253		if (!isset($this->metadata['require']['php']))
254		{
255			throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('require php'));
256		}
257
258		return true;
259	}
260}
261