Skip to content

Commit f676668

Browse files
committed
Merge pull request #2 from plugowski/feature/snippets
Feature/snippets
2 parents 2273249 + 506e984 commit f676668

9 files changed

Lines changed: 569 additions & 4 deletions

File tree

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ Simple and usefull tool to execute custom php scripts. It allows to block specif
88

99
## Installation
1010

11-
Clone that repository wherever you want (in my example `/www/php_sandbox`), then in base dir run:
11+
Clone that repository wherever you want (in my example `/www/php_sandbox`)
12+
13+
```
14+
$ cd /www
15+
$ git clone git@github.com:plugowski/php_sandbox.git
16+
```
17+
18+
and load all dependencies:
19+
1220

1321
```
1422
composer update --no-dev
@@ -57,6 +65,13 @@ Ctrl-S | Command-S | Execute code
5765

5866
## Changelog
5967

68+
- 1.2
69+
- fixed counting of memory used by script (now it is counting only for evaluated script without extra stuff from bootstrap)
70+
- added new PhpStorm shortcut
71+
- changed routing from FatFree to my own (FatFree fired couple ini_sets which might conflict with security settings, where
72+
ini_set() function will be disabled)
73+
- added snippets, which you can save and load in any time
74+
- added possibility to switch between couple of php versions
6075
- 1.1
6176
- added Kint debug tool for dumping varialbles
6277
- refactored Config class

src/Evaluator/Snippet.php

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?php
2+
namespace PhpSandbox\Evaluator;
3+
4+
/**
5+
* Class Snippet
6+
* @package Evaluator
7+
*/
8+
class Snippet
9+
{
10+
/**
11+
* @var Config
12+
*/
13+
private $config;
14+
15+
/**
16+
* Snippet constructor.
17+
* @param Config $config
18+
*/
19+
public function __construct(Config $config)
20+
{
21+
$this->config = $config;
22+
}
23+
24+
/**
25+
* @param string|null $subFolder
26+
* @return string
27+
*/
28+
private function getSnippetsDir($subFolder = null)
29+
{
30+
$dir = $this->config->read('snippets_dir');
31+
32+
if (!is_null($subFolder)) {
33+
$dir .= DIRECTORY_SEPARATOR . $subFolder;
34+
}
35+
36+
if (!file_exists($dir)) {
37+
mkdir($dir, 0755, true);
38+
}
39+
return $dir;
40+
}
41+
42+
/**
43+
* Validate name of created snippet
44+
*
45+
* @param string $name
46+
* @throws SnippetException
47+
*/
48+
private function validateName($name)
49+
{
50+
if (substr_count($name, '/') > 1) {
51+
throw new SnippetException("Only one level of folders are allowed.", SnippetException::MAX_NESTING);
52+
}
53+
if (!preg_match('/^(\w+\/)?[\w\.]+$/', $name)) {
54+
throw new SnippetException("Name contains not allowed characters!", SnippetException::WRONG_NAME);
55+
}
56+
if (false === strpos($name, '.php')) {
57+
throw new SnippetException("Extension is required and should be *.php", SnippetException::MISSING_EXTENSION);
58+
}
59+
}
60+
61+
/**
62+
* Save posted code into snippet
63+
*
64+
* @param string $name
65+
* @param string $code
66+
* @return bool
67+
*/
68+
public function save($name, $code)
69+
{
70+
$this->validateName($name);
71+
72+
$dir = null;
73+
if (false !== strpos($name, '/')) {
74+
list($dir, $name) = explode('/', $name);
75+
}
76+
77+
$fh = fopen($this->getSnippetsDir($dir) . DIRECTORY_SEPARATOR . $name, 'w+');
78+
fwrite($fh, $code);
79+
fclose($fh);
80+
81+
return true;
82+
}
83+
84+
/**
85+
* Return snippet content
86+
*
87+
* @param array $params
88+
*/
89+
public function load($params)
90+
{
91+
$fullName = $this->getSnippetsDir() . $params['filename'];
92+
93+
if (file_exists($fullName)) {
94+
$code = file_get_contents($fullName);
95+
}
96+
97+
echo json_encode(compact('code'));
98+
}
99+
100+
/**
101+
* Delete snippet
102+
*
103+
* @param array $params
104+
* @throws SnippetException
105+
* @return bool
106+
*/
107+
public function delete($params)
108+
{
109+
$fullName = $this->getSnippetsDir() . $params['filename'];
110+
111+
if (!file_exists($fullName)) {
112+
throw new SnippetException("File does not exists!", SnippetException::FILE_NOT_EXISTS);
113+
}
114+
if (!is_writable($fullName)) {
115+
throw new SnippetException("No permission to modify file!", SnippetException::NO_PERMISSION);
116+
}
117+
118+
unlink($fullName);
119+
120+
$parts = explode('/', $fullName);
121+
array_pop($parts);
122+
$dir = implode('/', $parts);
123+
124+
// if dir is empty, remove it
125+
if (count(scandir($dir)) == 2) {
126+
rmdir($dir);
127+
}
128+
129+
return true;
130+
}
131+
132+
/**
133+
* Return list of all snippets
134+
*
135+
* @return array
136+
*/
137+
public function getList()
138+
{
139+
$files = $this->recursiveScan($this->getSnippetsDir());
140+
return $this->prepareArrayToJson($files);
141+
}
142+
143+
/**
144+
* Build JSON with structure of folders and files
145+
*
146+
* @param $files
147+
* @return array
148+
*/
149+
private function prepareArrayToJson($files)
150+
{
151+
$tmp = [];
152+
foreach ($files as $k => $value) {
153+
154+
$name = $value;
155+
$type = 'file';
156+
$data = [];
157+
158+
if (preg_match('/^>\s/', $k)) {
159+
$type = 'folder';
160+
$name = str_replace('> ', '', $k);
161+
$data = $this->prepareArrayToJson($value);
162+
}
163+
164+
$tmp[] = ['name' => $name, 'type' => $type, 'data' => $data];
165+
}
166+
return $tmp;
167+
}
168+
169+
/**
170+
* Recursive scan selected folder and list all files and folders
171+
*
172+
* @param string $dir
173+
* @return array
174+
*/
175+
private function recursiveScan($dir)
176+
{
177+
$files = scandir($dir);
178+
$tmp = [];
179+
180+
foreach ($files as $file) {
181+
if (in_array($file, ['.', '..'])) {
182+
continue;
183+
}
184+
185+
if (is_dir($dir . DIRECTORY_SEPARATOR . $file)) {
186+
$tmp['> ' . $file] = $this->recursiveScan($dir . DIRECTORY_SEPARATOR . $file);
187+
} else {
188+
$tmp[] = $file;
189+
}
190+
}
191+
ksort($tmp);
192+
return $tmp;
193+
}
194+
}

src/Evaluator/SnippetException.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
namespace PhpSandbox\Evaluator;
3+
4+
use Exception;
5+
6+
/**
7+
* Class SnippetException
8+
* @package Evaluator
9+
*/
10+
class SnippetException extends Exception
11+
{
12+
const MAX_NESTING = 1001;
13+
const WRONG_NAME = 1002;
14+
const MISSING_EXTENSION = 1003;
15+
const FILE_NOT_EXISTS = 1004;
16+
const NO_PERMISSION = 1005;
17+
18+
/**
19+
* SnippetException constructor.
20+
* @param string $message
21+
* @param int $code
22+
* @param Exception $previous
23+
*/
24+
public function __construct($message = "", $code = 0, Exception $previous = null)
25+
{
26+
parent::__construct($message, $code, $previous);
27+
}
28+
}

src/config.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
*/
2424
'tmp_dir' => '/tmp/php_sandbox/',
2525

26+
/**
27+
* Dir where all snippets will be stored
28+
*/
29+
'snippets_dir' => '/tmp/php_sandbox/snippets/',
30+
2631
/**
2732
* Pre-execute scripts
2833
*/

webroot/actions.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use PhpRouter\RouteRequest;
99
use PhpSandbox\Evaluator\Config;
1010
use PhpSandbox\Evaluator\Evaluator;
11+
use PhpSandbox\Evaluator\Snippet;
12+
use PhpSandbox\Evaluator\SnippetException;
1113

1214
// load config file
1315
$config = new Config(__DIR__ . '/../src/config.php');
@@ -51,6 +53,32 @@
5153
}
5254
}));
5355

56+
/**
57+
* Validate and save new snippet
58+
*/
59+
$routing->attach(new Route('POST /save_snippet.json [ajax]', function() use ($config){
60+
61+
if (!empty($_POST['name']) && !empty($_POST['code'])) {
62+
try {
63+
(new Snippet($config))->save($_POST['name'], $_POST['code']);
64+
echo json_encode(['status' => 'success']);
65+
} catch (SnippetException $e) {
66+
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
67+
}
68+
}
69+
}));
70+
71+
/**
72+
* Get snippets list
73+
*/
74+
$routing->attach(new Route('GET /get_snippets_list.json', function() use ($config) {
75+
$snippets = (new Snippet($config))->getList();
76+
echo json_encode($snippets);
77+
}));
78+
79+
$routing->attach(new Route('GET /get_snippet/@filename', ['filename' => '[/\w]+.php'], '\PhpSandbox\Evaluator\Snippet->load', [$config]));
80+
$routing->attach(new Route('DELETE /delete_snippet/@filename', ['filename' => '[/\w]+.php'], '\PhpSandbox\Evaluator\Snippet->delete', [$config]));
81+
5482
/**
5583
* Get snippets list
5684
*/

0 commit comments

Comments
 (0)