1<?php
2// must be run within Dokuwiki
3if(!defined('DOKU_INC')) die();
4
5if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
6require_once(DOKU_PLUGIN.'syntax.php');
7
8// implode, explode delimiter
9define('GITLOG_DELIMITER', '|');
10
11class syntax_plugin_gitlog extends DokuWiki_Syntax_Plugin
12{
13	function getType()
14	{
15		return 'substition';
16	}
17
18	function getSort()
19	{
20		return 999;
21	}
22
23 	/**
24 	 * Registers the regular expressions
25 	 * @param  mixed $mode
26 	 * @return void
27 	 */
28	function connectTo($mode)
29	{
30		$this->Lexer->addSpecialPattern('<gitlog:.+?>', $mode, 'plugin_gitlog');
31	}
32
33	/**
34	 * Prepares the matched syntax for use in the renderer
35	 * @param  mixed $match
36	 * @param  mixed $state
37	 * @param  mixed $pos
38	 * @param  Doku_Handler $handler
39	 * @return array
40	 */
41	function handle($match, $state, $pos, Doku_Handler $handler)
42	{
43		// default value
44		$parameters = array();
45
46		// regex
47		preg_match_all('#(\w+)\s*=\s*"(.*?)"#', $match, $return);
48
49		if (is_array($return) && isset($return[1]) && is_array($return[1]))
50		foreach($return[1] as $index => $name)
51		{
52			$parameters[$name] = $return[2][$index];
53		}
54
55		return $parameters;
56	}
57
58 	/**
59 	 * Renders Plugin Output
60 	 * @param  string        $mode
61 	 * @param  Doku_Renderer $renderer
62 	 * @param  array         $data
63 	 * @return bool
64 	 */
65	function render($mode, Doku_Renderer $renderer, $data)
66	{
67		if($mode == 'xhtml')
68		{
69			try {
70
71				// check if repository is set
72				if( ! isset($data['repository'])) {
73					throw new Exception('no repository set', 1);
74				}
75
76				// check limit parameter
77				if(isset($data['limit']) && is_numeric($data['limit'])) {
78					$limit = (int)($data['limit']);
79				} else {
80					$limit = 10;
81				}
82
83				// check bare parameter
84				if (empty($data['bare'])) {
85					$bare=false;
86				} else {
87					$bare=true;
88				}
89
90				// if a dir parameter is set, use this instead of config value
91				if ( ! empty($data['dir']) ) {
92					$repository = $this->clean_git_dir($data['dir']).$data['repository'];
93				} else {
94					$repository = $this->clean_git_dir($this->getConf('root_dir')).$data['repository'];
95				}
96
97				// check if path is invalid
98				if ( ! is_dir(dirname($repository)) ) {
99					throw new Exception('repository path not valid >> '.$repository, 1);
100				}
101
102				// get the git log and changed files
103				$log = $this->git_log($repository, $limit, $bare);
104
105				// start rendering
106				$renderer->doc .= '<ul class="gitlogplugin">';
107
108				foreach($log as $row)
109				{
110					$renderer->doc .= '<li class="commit"><div class="message">';
111					$renderer->doc .= hsc($row['message']);
112					$renderer->doc .= '</div><div class="meta">';
113					$renderer->doc .= hsc($row['author']).' : '.date($this->getConf('date_format'), $row['timestamp']);
114					$renderer->doc .= '</div>';
115
116					// render changed file list if any
117					if ( ! empty($row['changedfiles']) ) {
118
119						$renderer->doc .= ' <a href="#" class="seechanges">[See Changes]</a>';
120
121						$renderer->doc .= '<ul class="changedfiles">';
122						foreach ($row['changedfiles'] as $changedfile) {
123							$renderer->doc .= '<li>'.hsc($changedfile).'</li>';
124						}
125						$renderer->doc .= '</ul>';
126					}
127
128					$renderer->doc .= '</li>';
129
130				}
131
132				$renderer->doc .= '</ul>';
133
134			} catch (Exception $e) {
135
136				$renderer->doc .= 'Error: ' . $e->getMessage();
137				return true;
138
139			}
140
141			return true;
142		}
143
144		return false;
145	}
146
147	/**
148	 * Main Function to get log and changed files
149	 * @param  string  $repo
150	 * @param  integer $limit
151	 * @param  boolean $bare
152	 * @return array
153	 */
154	function git_log($repo, $limit = 10, $bare=false)
155	{
156		$format = array('%H', '%at', '%an', '%s');
157		$params = implode(GITLOG_DELIMITER, $format);
158		$data = $this->run_git('log --pretty=format:"'.$params.'" -'.$limit, $repo, $bare);
159		$result = array();
160
161		foreach($data as $line)
162		{
163			// explode
164			$columns = explode(GITLOG_DELIMITER, $line);
165
166			// run git show command
167			$changedfiles = $this->run_git('show --pretty="format:" --name-only '.$columns[0], $repo, $bare);
168
169			$row = array(
170				'commit' => $columns[0],
171				'timestamp' => $columns[1],
172				'author' => $columns[2],
173				'message' => $columns[3],
174				'changedfiles' => $this->cleanup_git_show($changedfiles),
175			);
176
177			$result[] = $row;
178		}
179
180		return $result;
181	}
182
183	/**
184	 * Runs a git command
185	 * @param  string  $command
186	 * @param  string  $repo
187	 * @param  boolean $bare
188	 * @return mixed
189	 */
190	function run_git($command, $repo, $bare=false)
191	{
192		// if not bare, add git folder
193		if ( ! $bare ) {
194			$repo .= DIRECTORY_SEPARATOR . '.git';
195		}
196
197		$output = array();
198		$ret = 0;
199		$c = $this->getConf('git_exec').' --git-dir="'.$repo.'" '.$command;
200		exec($c, $output, $ret);
201
202		if ($ret != 0) {
203
204			//an error
205
206			$exceptionmessage = "The following command failed:<br>";
207			$exceptionmessage .= $c . "<br>";
208			$exceptionmessage .= "Please check configuration and/or path!";
209
210			throw new Exception($exceptionmessage, 1);
211
212		}
213
214		return $output;
215	}
216
217	/**
218	 * Removes empty elements from Array
219	 * @param  array $input
220	 * @return array
221	 */
222	function cleanup_git_show(Array $input)
223	{
224		return array_filter($input, array($this, 'remove_empty'));
225	}
226
227	/**
228	 * Array filter, removes empty elements
229	 * @param  mixed $value
230	 * @return mixed
231	 */
232	function remove_empty($value)
233	{
234		return !empty($value) || $value === 0;
235	}
236
237	/**
238	 * Cleans the git_dir string and
239	 * removes possible errors
240	 * @param  string $value
241	 * @return string
242	 */
243	function clean_git_dir($value)
244	{
245		$value = trim($value, "'");
246		return rtrim($value, "\/").DIRECTORY_SEPARATOR;
247	}
248}