make mime type detection work on php-fpm which has no PATH env variable
[phorkie.git] / src / phorkie / Repository / Post.php
1 <?php
2 namespace phorkie;
3
4 class Repository_Post
5 {
6     public $repo;
7
8     public function __construct(Repository $repo = null)
9     {
10         $this->repo = $repo;
11     }
12
13     /**
14      * Processes the POST data, changes description and files
15      *
16      * @return boolean True if the post was successful
17      */
18     public function process($postData, $sessionData)
19     {
20         if (!isset($postData['files'])) {
21             return false;
22         }
23         if (!$this->hasContent($postData)) {
24             return false;
25         }
26
27         if (!$this->repo) {
28             $this->repo = $this->createRepo();
29         }
30
31         $vc = $this->repo->getVc();
32
33
34         $bChanged = false;
35         $bCommit  = false;
36         if ($postData['description'] != $this->repo->getDescription()) {
37             $this->repo->setDescription($postData['description']);
38             $bChanged = true;
39         }
40
41         foreach ($postData['files'] as $num => $arFile) {
42             $bUpload = false;
43             if ($_FILES['files']['error'][$num]['upload'] == 0) {
44                 //valid file upload
45                 $bUpload = true;
46             } else if ($arFile['content'] == '' && $arFile['name'] == '') {
47                 //empty (new) file
48                 continue;
49             }
50
51             $orignalName = Tools::sanitizeFilename($arFile['original_name']);
52             $name        = Tools::sanitizeFilename($arFile['name']);
53
54             if ($arFile['type'] == '_auto_') {
55                 //FIXME: upload
56                 $arFile['type'] = $this->getType($arFile['content']);
57             }
58
59             if ($name == '') {
60                 if ($bUpload) {
61                     $name = Tools::sanitizeFilename(
62                         $_FILES['files']['name'][$num]['upload']
63                     );
64                 } else {
65                     $name = $this->getNextNumberedFile('phork')
66                         . '.' . $arFile['type'];
67                 }
68             }
69
70             $bNew = false;
71             $bDelete = false;
72             if (!isset($orignalName) || $orignalName == '') {
73                 //new file
74                 $bNew = true;
75                 if (strpos($name, '.') === false) {
76                     //automatically append file extension if none is there
77                     $name .= '.' . $arFile['type'];
78                 }
79             } else if (!$this->repo->hasFile($orignalName)) {
80                 //unknown file
81                 //FIXME: Show error message
82                 continue;
83             } else if (isset($arFile['delete']) && $arFile['delete'] == 1) {
84                 $bDelete = true;
85             } else if ($orignalName != $name) {
86                 if (strpos($name, '/') === false) {
87                     //ignore names with a slash in it, would be new directory
88                     //FIXME: what to do with overwrites?
89                     $vc->getCommand('mv')
90                         ->addArgument($orignalName)
91                         ->addArgument($name)
92                         ->execute();
93                     $bCommit = true;
94                 } else {
95                     $name = $orignalName;
96                 }
97             }
98
99             $file = $this->repo->getFileByName($name, false);
100             if ($bDelete) {
101                 $command = $vc->getCommand('rm')
102                     ->addArgument($file->getFilename())
103                     ->execute();
104                 $bCommit = true;
105             } else if ($bUpload) {
106                 move_uploaded_file(
107                     $_FILES['files']['tmp_name'][$num]['upload'],
108                     $file->getFullPath()
109                 );
110                 $command = $vc->getCommand('add')
111                     ->addArgument($file->getFilename())
112                     ->execute();
113                 $bCommit = true;
114             } else if ($bNew
115                 || (isset($arFile['content'])
116                     && $file->getContent() != $arFile['content']
117                 )
118             ) {
119                 $dir = dirname($file->getFullPath());
120                 if (!is_dir($dir)) {
121                     mkdir($dir, 0777, true);
122                 }
123                 file_put_contents($file->getFullPath(), $arFile['content']);
124                 $command = $vc->getCommand('add')
125                     ->addArgument($file->getFilename())
126                     ->execute();
127                 $bCommit = true;
128             }
129         }
130
131         if (isset($sessionData['identity'])) {
132             $notes = $sessionData['identity'];
133         } else {
134             $notes = $sessionData['ipaddr'];
135         }
136
137         if ($bCommit) {
138             $vc->getCommand('commit')
139                 ->setOption('message', '')
140                 ->setOption('allow-empty-message')
141                 ->setOption('no-edit')
142                 ->setOption(
143                     'author',
144                     $sessionData['name'] . ' <' . $sessionData['email'] . '>'
145                 )
146                 ->execute();
147             //FIXME: git needs ref BEFORE add
148             //quick hack until http://pear.php.net/bugs/bug.php?id=19605 is fixed
149             //also waiting for https://pear.php.net/bugs/bug.php?id=19623
150             $vc->getCommand('notes --ref=identity add')
151                 ->setOption('force')
152                 ->setOption('message', "$notes")
153                 ->execute();
154             //update info for dumb git HTTP transport
155             //the post-update hook should do that IMO, but does not somehow
156             $vc->getCommand('update-server-info')->execute();
157
158             $bChanged = true;
159         }
160
161         if ($bChanged) {
162             //FIXME: index changed files only
163             //also handle file deletions
164             $db = new Database();
165             $not = new Notificator();
166             if ($bNew) {
167                 $db->getIndexer()->addRepo($this->repo);
168                 $not->create($this->repo);
169             } else {
170                 $commits = $this->repo->getHistory();
171                 $db->getIndexer()->updateRepo(
172                     $this->repo,
173                     $commits[count($commits)-1]->committerTime,
174                     $commits[0]->committerTime
175                 );
176                 $not->edit($this->repo);
177             }
178         }
179
180         return true;
181     }
182
183     protected function hasContent($postData)
184     {
185         foreach ($postData['files'] as $num => $arFile) {
186             if ($_FILES['files']['error'][$num]['upload'] == 0) {
187                 return true;
188             }
189             if (isset($arFile['content']) && $arFile['content'] != '') {
190                 return true;
191             }
192             if (isset($arFile['name']) && $arFile['name'] != '') {
193                 //binary files do not have content
194                 return true;
195             }
196             if (isset($arFile['delete']) && $arFile['delete'] != '') {
197                 //binary files do not have content
198                 return true;
199             }
200         }
201         return false;
202     }
203
204     public function createRepo()
205     {
206         $rs = new Repositories();
207         $repo = $rs->createNew();
208         $vc = $repo->getVc();
209         $vc->getCommand('init')
210             //this should be setOption, but it fails with a = between name and value
211             ->addArgument('--separate-git-dir')
212             ->addArgument(
213                 $GLOBALS['phorkie']['cfg']['gitdir'] . '/' . $repo->id . '.git'
214             )
215             ->addArgument($repo->workDir)
216             ->execute();
217
218         $rs = new Repository_Setup($repo);
219         $rs->afterInit();
220
221         return $repo;
222     }
223
224     public function getNextNumberedFile($prefix)
225     {
226         $num = -1;
227         do {
228             ++$num;
229             $files = glob($this->repo->workDir . '/' . $prefix . $num . '.*');
230         } while (count($files));
231
232         return $prefix . $num;
233     }
234
235     public function getType($content, $returnError = false)
236     {
237         if (getenv('PATH') == '') {
238             //php-fpm does not fill $PATH by default
239             // we have to work around that since System::which() uses it
240             putenv('PATH=/usr/local/bin:/usr/bin:/bin');
241         }
242
243         $tmp = tempnam(sys_get_temp_dir(), 'phorkie-autodetect-');
244         file_put_contents($tmp, $content);
245         $type = Tool_MIME_Type_PlainDetect::autoDetect($tmp);
246         unlink($tmp);
247
248         if ($returnError && $type instanceof \PEAR_Error) {
249             return $type;
250         }
251
252         return $this->findExtForType($type);
253     }
254
255     protected function findExtForType($type)
256     {
257         $ext = 'txt';
258         foreach ($GLOBALS['phorkie']['languages'] as $lext => $arLang) {
259             if ($arLang['mime'] == $type) {
260                 $ext = $lext;
261                 break;
262             }
263         }
264         return $ext;
265     }
266 }
267
268 ?>