1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Esther Brunner <wikidesign@gmail.com>
5 */
6
7class admin_plugin_linkback extends DokuWiki_Admin_Plugin {
8
9  function getMenuSort(){ return 201; }
10  function forAdminOnly(){ return false; }
11
12  function handle(){
13    global $lang;
14
15    $lid = $_REQUEST['lid'];
16    if (is_array($lid)) $lid = array_keys($lid);
17
18    /** @var action_plugin_linkback_display $action */
19    $action = plugin_load('action', 'linkback_display');
20    if (!$action) return; // couldn't load action plugin component
21
22    switch ($_REQUEST['linkback']){
23      case $lang['btn_delete']:
24        $action->_deleteLinkbacks($lid);
25        break;
26
27      case $this->getLang('btn_show'):
28        $action->_changeLinkbackVisibilities($lid, true);
29        break;
30
31      case $this->getLang('btn_hide'):
32        $action->_changeLinkbackVisibilities($lid, false);
33        break;
34
35      case $this->getLang('btn_ham'):
36      	$action->_markLinkbacks($lid, false);
37      	break;
38
39      case $this->getLang('btn_spam'):
40      	$action->_markLinkbacks($lid, true);
41      	break;
42
43      case $this->getLang('btn_change'):
44        $this->_changeStatus($_REQUEST['status']);
45        break;
46    }
47  }
48
49  function html(){
50    global $conf;
51
52    $first = $_REQUEST['first'];
53    if (!is_numeric($first)) $first = 0;
54    $num = $conf['recent'];
55
56    ptln('<h1>'.$this->getLang('menu').'</h1>');
57
58    $targets = $this->_getTargets();
59
60    // slice the needed chunk of linkback targets
61    $more = count($targets) > ($first + $num);
62    $targets = array_slice($targets, $first, $num);
63
64    foreach ($targets as $target){
65      $linkbacks = $this->_getLinkbacks($target);
66      $this->_targetHead($target);
67      if ($linkbacks === false){
68        ptln('</div>', 6); // class="level2"
69        continue;
70      }
71
72      ptln('<form method="post" action="'.wl($target['id']).'">', 8);
73      ptln('<div class="no">', 10);
74      ptln('<input type="hidden" name="do" value="admin" />', 10);
75      ptln('<input type="hidden" name="page" value="linkback" />', 10);
76      echo html_buildlist($linkbacks, 'admin_linkback', array($this, '_linkbackItem'), array($this, '_li_linkback'));
77      $this->_actionButtons($target['id']);
78    }
79
80    $this->_browseLinkbackLinks($more, $first, $num);
81
82    if(count($targets) === 0) {
83        echo 'No linkback targets';
84    }
85  }
86
87  /**
88   * Returns an array of targets with linkback features, sorted by recent linkbacks
89   */
90  function _getTargets(){
91    global $conf;
92
93    // returns the list of pages in the given namespace and it's subspaces
94    $items = array();
95    search($items, $conf['datadir'], 'search_allpages', array());
96
97    // add pages with comments to result
98    $result = array();
99    foreach ($items as $item){
100      $id = $item['id'];
101
102      // some checks
103      $file = metaFN($id, '.linkbacks');
104      if (!@file_exists($file)) continue; // skip if no comments file
105
106      $date = filemtime($file);
107      $result[] = array(
108        'id'   => $id,
109        'file' => $file,
110        'date' => $date,
111      );
112    }
113
114    // finally sort by time of last comment
115    usort($result, array('admin_plugin_linkback', '_targetCmp'));
116
117    return $result;
118  }
119
120  /**
121   * Callback for comparison of target data.
122   *
123   * Used for sorting targets in descending order by date of last linkback.
124   * If this date happens to be equal for the compared targest, page id
125   * is used as second comparison attribute.
126   */
127  function _targetCmp($a, $b) {
128  	if ($a['date'] == $b['date']) {
129        return strcmp($a['id'], $b['id']);
130    }
131    return ($a['date'] < $b['date']) ? 1 : -1;
132  }
133
134  /**
135   * Outputs header, page ID and status of linkbacks
136   */
137  function _targetHead($target){
138    $id = $target['id'];
139
140    $labels = array(
141      'send' => $this->getLang('send'),
142      'receive' => $this->getLang('receive'),
143      'display' => $this->getLang('display')
144    );
145    $title = p_get_metadata($id, 'title');
146    if (!$title) $title = $id;
147    ptln('<h2 name="'.$id.'" id="'.$id.'">'.hsc($title).'</h2>', 6);
148    ptln('<form method="post" action="'.wl($id).'">', 6);
149    ptln('<div class="mediaright">', 8);
150    ptln('<input type="hidden" name="do" value="admin" />', 10);
151    ptln('<input type="hidden" name="page" value="linkback" />', 10);
152    ptln($this->getLang('status').': ', 10);
153    foreach ($labels as $key => $label){
154      $selected = (($target[$key]) ? ' checked="checked"' : '');
155      ptln('<label for="status_'.$id.'_'.$key.'">'.$label.'</label>&nbsp;<input type="checkbox" name="status['.$key.']" value="1"'.$selected.' id="status_'.$id.'_'.$key.'" />&nbsp;&nbsp;', 12);
156    }
157    ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_change').'" class"button" title="'.$this->getLang('btn_change').'" />', 10);
158    ptln('</div>', 8);
159    ptln('</form>', 6);
160    ptln('<div class="level2">', 6);
161    ptln('<a href="'.wl($id).'" class="wikilink1">'.$id.'</a> ', 8);
162    return true;
163  }
164
165  /**
166   * Returns the full comments data for a given wiki page
167   */
168  function _getLinkbacks(&$target){
169    $id = $target['id'];
170
171    if (!$target['file']) $target['file'] = metaFN($id, '.linkbacks');
172    if (!@file_exists($target['file'])) return false; // no discussion thread at all
173
174    $data = unserialize(io_readFile($target['file'], false));
175
176    $target['send'] = $data['send'];
177    $target['receive'] = $data['receive'];
178    $target['display'] = $data['display'];
179    $target['number'] = $data['number'];
180
181    if (!$data['display']) return false;   // comments are turned off
182    if (!$data['receivedpings']) return false; // no comments
183
184    $result = array();
185    foreach($data['receivedpings'] as $lid => $linkback) {
186        $linkback['level'] = 1;
187        $result[$lid] = $linkback;
188    }
189
190    if (empty($result)) return false;
191    else return $result;
192  }
193
194  /**
195   * Checkbox and info about a linkback item
196   */
197  function _linkbackItem($linkback){
198    global $conf;
199
200    // prepare variables
201    $title = $linkback['title'];
202    $url = $linkback['url'];
203    $date = $linkback['received'];
204    $excerpt = $linkback['excerpt'];
205    $type = $linkback['type'];
206
207    if (utf8_strlen($excerpt) > 160) $excerpt = utf8_substr($excerpt, 0, 160).'...';
208
209    return '<input type="checkbox" name="lid['.$linkback['lid'].']" value="1" /> '.
210      $this->external_link($url, $title).', '.strftime($conf['dformat'], $date).', ' . $this->getLang('linkback_type_' . $type) . ': '.
211      '<span class="excerpt">'.$excerpt.'</span>';
212  }
213
214  /**
215   * list item tag
216   */
217  function _li_linkback($linkback){
218    if (!$linkback['show'])
219    	return '<li class="hidden">';
220    return '<li>';
221  }
222
223  /**
224   * Show buttons to bulk remove, hide or show comments
225   */
226  function _actionButtons($id){
227    ptln('<div class="linkback_buttons">', 12);
228    ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_show').'" class="button" title="'.$this->getLang('btn_show').'" />', 14);
229    ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_hide').'" class="button" title="'.$this->getLang('btn_hide').'" />', 14);
230    ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_ham').'" class="button" title="'.$this->getLang('btn_ham').'" />', 14);
231    ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_spam').'" class="button" title="'.$this->getLang('btn_spam').'" />', 14);
232    ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_delete').'" class="button" title="'.$this->getLang('btn_delete').'" />', 14);
233    ptln('</div>', 12); // class="comment_buttons"
234    ptln('</div>', 10); // class="no"
235    ptln('</form>', 8);
236    ptln('</div>', 6); // class="level2"
237    return true;
238  }
239
240  /**
241   * Displays links to older newer discussions
242   */
243  function _browseLinkbackLinks($more, $first, $num){
244    global $ID;
245
246    if (($first == 0) && (!$more)) return true;
247
248    $params = array('do' => 'admin', 'page' => 'linkback');
249    $last = $first+$num;
250    ptln('<div class="level1">', 8);
251    if ($first > 0){
252      $first -= $num;
253      if ($first < 0) $first = 0;
254      $params['first'] = $first;
255      ptln('<p class="centeralign">', 8);
256      $ret = '<a href="'.wl($ID, $params).'" class="wikilink1">&lt;&lt; '.$this->getLang('newer').'</a>';
257      if ($more){
258        $ret .= ' | ';
259      } else {
260        ptln($ret, 10);
261        ptln('</p>', 8);
262      }
263    } else if ($more){
264      ptln('<p class="centeralign">', 8);
265    }
266    if ($more){
267      $params['first'] = $last;
268      $ret .= '<a href="'.wl($ID, $params).'" class="wikilink1">'.$this->getLang('older').' &gt;&gt;</a>';
269      ptln($ret, 10);
270      ptln('</p>', 8);
271    }
272    ptln('</div>', 6); // class="level1"
273    return true;
274  }
275
276  /**
277   * Changes the status of a comment
278   */
279  function _changeStatus($new){
280    global $ID;
281
282    // get discussion meta file name
283    $file = metaFN($ID, '.linkbacks');
284    $data = unserialize(io_readFile($file, false));
285
286    $updated = false;
287    $updateText = false;
288    foreach (array('send', 'receive', 'display') as $key) {
289        if (!isset($new[$key]))
290            $new[$key] = false;
291
292        if ($new[$key] == $data[$key])
293            continue;
294
295        $data[$key] = $new[$key];
296        $updated = true;
297
298        if (in_array($key, array('send', 'receive')))
299            $updateText = true;
300    }
301
302    if (!$updated)
303        return false;
304
305    // save the comment metadata file
306    io_saveFile($file, serialize($data));
307
308    if (!$updateText)
309        return true;
310
311    // look for ~~LINKBACK~~ command in page file and change it accordingly
312    if ($data['receive'] && $data['display'])
313        $replace = '~~LINKBACK~~';
314    else if (!$data['receive'])
315        $replace = '~~LINKBACK:closed~~';
316    else
317        $replace = '~~LINKBACK:off~~';
318    $wiki = preg_replace('/~~LINKBACK([\w:]*)~~/', $replace, rawWiki($ID));
319    saveWikiText($ID, $wiki, $this->getLang('statuschanged'), true);
320
321    return true;
322  }
323
324}
325
326//Setup VIM: ex: et ts=4 enc=utf-8 :
327