1<?php
2if (!defined('DOKU_INC')) { die(); }
3
4/**
5 * DokuWiki Plugin matrixnotifier (Helper Component)
6 *
7 * @license GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html)
8 *
9 * @author Wilhelm/ JPTV.club
10 */
11
12class helper_plugin_matrixnotifier extends \dokuwiki\Extension\Plugin
13{
14	CONST __PLUGIN_VERSION__ = '1.2';
15
16	private $_event   = null;
17	private $_summary = null;
18	private $_payload = null;
19
20	private function valid_namespace()
21	{
22		$validNamespaces = $this->getConf('namespaces');
23		if (!empty($validNamespaces))
24		{
25			$validNamespacesArr = array_map('trim', explode(',', $validNamespaces));
26			$thisNamespaceArr   = explode(':', $GLOBALS['INFO']['namespace']);
27
28			return in_array($thisNamespaceArr[0], $validNamespacesArr);
29		}
30
31		return true;
32	}
33
34	private function check_event($event)
35	{
36		$etype = $event->data['changeType'];
37
38		if (($etype == 'C') && ($this->getConf('notify_create') == 1))
39		{
40			$this->_event = 'create';
41		}
42		elseif (($etype == 'E') && ($this->getConf('notify_edit') == 1))
43		{
44			$this->_event = 'edit';
45		}
46		elseif (($etype == 'e') && ($this->getConf('notify_edit') == 1) && ($this->getConf('notify_edit_minor') == 1))
47		{
48			$this->_event = 'edit minor';
49		}
50		elseif (($etype == 'D') && ($this->getConf('notify_delete') == 1))
51		{
52			$this->_event = 'delete';
53		}
54		/*
55		elseif (($etype == 'R') && ($this->getConf('notify_revert') == 1))
56		{
57			$this->_event = 'revert';
58			return true;
59		}
60		*/
61		else
62		{
63			return false;
64		}
65
66		$summary = $event->data['summary'];
67		if (!empty($summary))
68		{
69			$this->_summary = $summary;
70		}
71
72		return true;
73	}
74
75	private function update_payload($event)
76	{
77		$user = $GLOBALS['INFO']['userinfo']['name'];
78		$link = $this->compose_url($event, null);
79		$page = $event->data['id'];
80
81		$data = [
82			'create'     => ['loc_title' => 't_created',   'loc_event' => 'e_created',   'emoji' => '��'],
83			'edit'       => ['loc_title' => 't_updated',   'loc_event' => 'e_updated',   'emoji' => '��'],
84			'edit minor' => ['loc_title' => 't_minor_upd', 'loc_event' => 'e_minor_upd', 'emoji' => '��'],
85			'delete'     => ['loc_title' => 't_removed',   'loc_event' => 'e_removed',   'emoji' => "\u{1F5D1}"],  /* 'Wastebasket' emoji */
86		];
87
88		$d          = $data[$this->_event];
89		$title      = $this->getLang($d['loc_title']);
90		$useraction = $user.' '.$this->getLang($d['loc_event']);
91
92		$descr_raw  = $title.' · '.$useraction.' "'.$page.'" ('.$link.')';
93		$descr_html = $d['emoji'].' <strong>'.htmlspecialchars($title).'</strong> · '.htmlspecialchars($useraction).' &quot;<a href="'.$link.'">'.htmlspecialchars($page).'</a>&quot;';
94
95		if (($this->_event != 'delete') && ($this->_event != 'create'))
96		{
97			$oldRev = $GLOBALS['INFO']['meta']['last_change']['date'];
98
99			if (!empty($oldRev))
100			{
101				$diffURL     = $this->compose_url($event, $oldRev);
102				$descr_raw  .= ' ('.$this->getLang('compare').': '.$diffURL.')';
103				$descr_html .= ' (<a href="'.$diffURL.'">'.$this->getLang('compare').'</a>)';
104			}
105		}
106
107		if (($this->_event != 'delete') && $this->getConf('notify_show_summary'))
108		{
109			$summary = strip_tags($this->_summary);
110
111			if ($summary)
112			{
113				$descr_raw  .= ' · '.$this->getLang('l_summary').': '.$summary;
114				$descr_html .= ' · '.$this->getLang('l_summary').': <i>'.$summary.'</i>';
115			}
116		}
117
118		$this->_payload = array(
119			'msgtype'        => 'm.text',
120			'body'           => $descr_raw,
121			'format'         => 'org.matrix.custom.html',
122			'formatted_body' => $descr_html,
123		);
124	}
125
126	private function compose_url($event = null, $rev = null)
127	{
128		$page       = $event->data['id'];
129		$userewrite = $GLOBALS['conf']['userewrite']; /* 0 = no rewrite, 1 = htaccess, 2 = internal */
130
131		if ((($userewrite == 1) || ($userewrite == 2)) && $GLOBALS['conf']['useslash'] == true)
132		{
133			$page = str_replace(":", "/", $page);
134		}
135
136		$url = sprintf(['%sdoku.php?id=%s', '%s%s', '%sdoku.php/%s'][$userewrite], DOKU_URL, $page);
137
138		if ($rev != null)
139		{
140			$url .= ('&??'[$userewrite])."do=diff&rev={$rev}";
141		}
142
143		return $url;
144	}
145
146	private function submit_payload()
147	{
148		$homeserver  = $this->getConf('homeserver');
149		$roomid      = $this->getConf('room');
150		$accesstoken = $this->getConf('accesstoken');
151
152		if (!($homeserver && $roomid && $accesstoken))
153		{
154			error_log('matrixnotifer: At least one of the required configuration options \'homeserver\', \'room\', or \'accesstoken\' is not set.');
155			return;
156		}
157
158		$homeserver = rtrim(trim($homeserver), '/');
159		$endpoint = $homeserver.'/_matrix/client/r0/rooms/'.$roomid.'/send/m.room.message/'.uniqid('docuwiki', true).'-'.md5(strval(random_int(0, PHP_INT_MAX)));
160
161		$json_payload = json_encode($this->_payload);
162		if (!is_string($json_payload))
163		{
164			return;
165		}
166
167		$ch = curl_init($endpoint);
168		if ($ch)
169		{
170			/*  Use a proxy, if defined
171			 *
172			 *  Note: still entirely untested, was full of very obvious bugs, so nobody
173			 *        has ever used this succesfully anyway
174			 */
175			$proxy = $GLOBALS['conf']['proxy'];
176			if (!empty($proxy['host']))
177			{
178				/* configure proxy address and port
179				 */
180				$proxyAddress = $proxy['host'].':'.$proxy['port'];
181				curl_setopt($ch, CURLOPT_PROXY,          $proxyAddress);
182				curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
183				curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
184
185				/* include username and password if defined
186				 */
187				if (!empty($proxy['user']) && !empty($proxy['pass']))
188				{
189					$proxyAuth = $proxy['user'].':'.conf_decodeString($proxy['pass']);
190					curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyAuth );
191				}
192			}
193
194			/* Submit Payload
195			 */
196			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
197			curl_setopt($ch, CURLOPT_HTTPHEADER, array(
198				'Content-type: application/json',
199				'Content-length: '.strlen($json_payload),
200				'User-agent: DocuWiki Matrix Notifier Plugin '.self::__PLUGIN_VERSION__,
201				'Authorization: Bearer '.$accesstoken,
202				'Cache-control: no-cache',
203			));
204			curl_setopt($ch, CURLOPT_POSTFIELDS, $json_payload);
205			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
206			curl_exec($ch);
207
208			curl_close($ch);
209		}
210	}
211
212	public function sendUpdate($event)
213	{
214		if((strpos($event->data['file'], 'data/attic') === false) && $this->valid_namespace() && $this->check_event($event))
215		{
216			$this->update_payload($event);
217			$this->submit_payload();
218		}
219	}
220}
221