965 lines
36 KiB
PHP
965 lines
36 KiB
PHP
![]() |
<?php
|
||
|
/**
|
||
|
* Plugin : Pagemove
|
||
|
* Version : 0.10 (2010-06-17)
|
||
|
*
|
||
|
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||
|
* @author Gary Owen,
|
||
|
*/
|
||
|
|
||
|
// must be run within Dokuwiki
|
||
|
if (!defined('DOKU_INC')) die();
|
||
|
|
||
|
if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
|
||
|
if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
|
||
|
if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
|
||
|
|
||
|
require_once DOKU_PLUGIN.'admin.php';
|
||
|
|
||
|
require_once(DOKU_INC.'inc/search.php');
|
||
|
|
||
|
|
||
|
class admin_plugin_pagemove extends DokuWiki_Admin_Plugin {
|
||
|
|
||
|
var $show_form = true;
|
||
|
var $have_rights = true;
|
||
|
var $locked_files = array();
|
||
|
var $errors = array();
|
||
|
var $opts = array();
|
||
|
var $text = '';
|
||
|
var $idsToDelete = array();
|
||
|
|
||
|
|
||
|
function getMenuSort() { return FIXME; }
|
||
|
function forAdminOnly() { return false; }
|
||
|
|
||
|
/**
|
||
|
* function constructor
|
||
|
*/
|
||
|
function admin_plugin_pagemove(){
|
||
|
// enable direct access to language strings
|
||
|
$this->setupLocale();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* return some info
|
||
|
*/
|
||
|
function getInfo(){
|
||
|
return array(
|
||
|
'author' => 'Gary Owen, Arno Puschmann, Christoph Jähnigen',
|
||
|
'email' => 'pagemove@gmail.com',
|
||
|
'date' => '2011-08-11',
|
||
|
'name' => 'Pagemove',
|
||
|
'desc' => $this->lang['desc'],
|
||
|
'url' => 'http://www.dokuwiki.org/plugin:pagemove',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Only show the menu text for pages we can move or rename.
|
||
|
*/
|
||
|
function getMenuText() {
|
||
|
global $INFO;
|
||
|
global $ID;
|
||
|
global $conf;
|
||
|
|
||
|
if( !$INFO['exists'] )
|
||
|
return $this->lang['menu'].' ('.$this->lang['pm_notexist'].')';
|
||
|
elseif( $ID == $conf['start'] )
|
||
|
return $this->lang['menu'].' ('.$this->lang['pm_notstart'].')';
|
||
|
elseif( !$INFO['writable'] )
|
||
|
return $this->lang['menu'].' ('.$this->lang['pm_notwrite'].')';
|
||
|
else
|
||
|
return $this->lang['menu'];
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* output appropriate html
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function html() {
|
||
|
global $lang;
|
||
|
|
||
|
ptln('<!-- Pagemove Plugin start -->');
|
||
|
if( $this->show_form ) {
|
||
|
ptln( $this->locale_xhtml('pagemove') );
|
||
|
//We didn't get here from submit.
|
||
|
if( $this->have_rights && count($this->locked_files) == 0 ) {
|
||
|
$this->_pm_form();
|
||
|
}
|
||
|
else {
|
||
|
ptln( '<p><strong>' );
|
||
|
if ( !$this->have_rights ) {
|
||
|
ptln( $this->errors[0].'<br>' );
|
||
|
}
|
||
|
$c = count($this->locked_files);
|
||
|
if ( $c == 1 ) {
|
||
|
ptln( $this->lang['pm_filelocked'].$this->locked_files[0].'<br>'.$this->lang['pm_tryagain'] );
|
||
|
}
|
||
|
elseif ( $c > 1 ) {
|
||
|
ptln( $this->lang['pm_fileslocked'] );
|
||
|
for ( $i = 0 ; $i < $c ; $i++ ) {
|
||
|
ptln ( ($i > 0 ? ', ' : '').$this->locked_files[$i] );
|
||
|
}
|
||
|
ptln( '<br>'.$this->lang['pm_tryagain'] );
|
||
|
}
|
||
|
ptln ( '</strong></p>' );
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// display the moved/renamed page
|
||
|
ptln( $this->render($this->text) );
|
||
|
}
|
||
|
ptln('<!-- Pagemove Plugin end -->');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* show the move and/or rename a page form
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_form() {
|
||
|
global $ID;
|
||
|
global $lang;
|
||
|
global $conf;
|
||
|
|
||
|
$ns = getNS($ID);
|
||
|
$name = noNS($ID);
|
||
|
|
||
|
ptln(' <div align="center">');
|
||
|
ptln(' <script language="Javascript">');
|
||
|
ptln(' function setradio( group, choice ) {');
|
||
|
ptln(' for ( i = 0 ; i < group.length ; i++ ) {');
|
||
|
ptln(' if ( group[i].value == choice )');
|
||
|
ptln(' group[i].checked = true;');
|
||
|
ptln(' }');
|
||
|
ptln(' }');
|
||
|
ptln(' </script>');
|
||
|
ptln(' <form name="frm" action="'.wl($ID).'" method="post">');
|
||
|
// output hidden values to ensure dokuwiki will return back to this plugin
|
||
|
ptln(' <input type="hidden" name="do" value="admin" />');
|
||
|
ptln(' <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
|
||
|
ptln(' <input type="hidden" name="id" value="'.$ID.'" />');
|
||
|
ptln(' <fieldset id="fieldset_page">');
|
||
|
ptln(' <legend><input type="radio" name="page_ns" id="page_ns_0" value="page" CHECKED> '. $this->lang['pm_movepage'] .'</legend>');
|
||
|
ptln(' <table border="0" id="table_page">');
|
||
|
|
||
|
//Show any errors
|
||
|
if (count($this->errors) > 0) {
|
||
|
ptln ('<tr><td bgcolor="red" colspan="3">');
|
||
|
foreach($this->errors as $error) {
|
||
|
ptln ($error.'<br>');
|
||
|
}
|
||
|
ptln ('</td></tr>');
|
||
|
}
|
||
|
//create a list of namespaces
|
||
|
ptln( ' <tr><td align="right" nowrap><label><span>'.$this->lang['pm_targetns'].'</span></label></td>');
|
||
|
ptln( ' <td width="25"><input type="radio" name="nsr" id="nsr_0" value="<old>" '.($_REQUEST['nsr'] != '<new>' ? 'CHECKED' : '').'></td>');
|
||
|
ptln( ' <td><select name="ns_for_page" id="nsr_select" onChange="setradio(document.frm.nsr, \'<old>\');setradio(document.frm.page_ns, \'page\');">');
|
||
|
$this->_pm_form_create_list_ns($ns);
|
||
|
|
||
|
ptln( " </select></td>\n </tr><tr>");
|
||
|
|
||
|
ptln( ' <td align="right" nowrap><label><span>'.$this->lang['pm_newtargetns'].'</span></label></td>');
|
||
|
ptln( ' <td width="25"><input type="radio" name="nsr" id="nsr_1" value="<new>" '.($_REQUEST['nsr'] == '<new>' ? 'CHECKED' : '').'></td>');
|
||
|
ptln( ' <td align="left" nowrap><input type="text" name="newns" id="newns" value="'.formtext($this->opts['newns']).'" class="edit" onClick="setradio(document.frm.nsr, \'<new>\');setradio(document.frm.page_ns, \'page\');" /></td>');
|
||
|
ptln( ' </tr>');
|
||
|
ptln( ' <tr>');
|
||
|
ptln( ' <td align="right" nowrap><label><span>'.$this->lang['pm_newname'].'</span></label></td>');
|
||
|
ptln(' <td width="25"></td>'); //<input type="radio" name="pageradio" value="<page>" '.($_REQUEST['pageradio']!= '<namespace>' ? 'CHECKED' : '').'>
|
||
|
ptln( ' <td align="left" nowrap><input type="text" name="pagename" id="pagename" value="'.formtext(isset($this->opts['newname']) ? $this->opts['newname'] : $name).'" class="edit" onClick="setradio(document.frm.page_ns, \'page\');" /></td>');
|
||
|
ptln( ' </tr>');
|
||
|
ptln( ' </tr>');
|
||
|
ptln( ' </tr>');
|
||
|
ptln( ' </table>');
|
||
|
ptln( ' </fieldset>');
|
||
|
|
||
|
ptln(' <br>');
|
||
|
ptln(' <fieldset id="fieldset_ns" >');
|
||
|
ptln(' <legend><input type="radio" name="page_ns" id="page_ns_1" value="ns"> '. $this->lang['pm_movens'] .'</legend>');
|
||
|
ptln(' <table border="0" id="table_ns">');
|
||
|
ptln( ' <tr><td align="right" nowrap><label><span>'.$this->lang['pm_targetns'].'</span></label></td>');
|
||
|
ptln( ' <td><select name="ns" id="ns_select" onChange="setradio(document.frm.page_ns, \'ns\');">');
|
||
|
$this->_pm_form_create_list_ns($ns);
|
||
|
ptln( " </select></td>\n </tr>");
|
||
|
ptln( ' <tr>');
|
||
|
ptln( ' <td align="right" nowrap><label><span>'.$this->lang['pm_newnsname'].'</span></label></td>');
|
||
|
ptln( ' <td align="left" nowrap><input type="text" name="namespacename" id="namespacename" value="'.formtext(isset($this->opts['newnsname']) ? $this->opts['newnsname'] : $this->opts['nsname']).'" class="edit" onClick="setradio(document.frm.page_ns, \'ns\');" /></td>');
|
||
|
ptln( ' </tr>');
|
||
|
ptln( ' </table>');
|
||
|
ptln(' </fieldset>');
|
||
|
ptln( '<br><center><input type="submit" value="'.formtext($this->lang['pm_submit']).'" class="button" /><input type="button" value="'.$this->lang['pm_preview'].'" class="button" onClick="Javascript:preview();"/></center>');
|
||
|
ptln( '</form>');
|
||
|
|
||
|
ptln('<font id="preview_output"></font>');
|
||
|
|
||
|
ptln(' <script language="Javascript">');
|
||
|
ptln(" table_page_width = document.getElementById('table_page').offsetWidth;");
|
||
|
ptln(" table_ns_width = document.getElementById('table_ns').offsetWidth;");
|
||
|
ptln(" max_width = Math.max(table_page_width,table_ns_width)+'px';");
|
||
|
ptln(" document.getElementById('fieldset_page').style.width = max_width;");
|
||
|
ptln(" document.getElementById('fieldset_ns').style.width = max_width;");
|
||
|
|
||
|
ptln("function preview(){");
|
||
|
ptln("if(document.getElementById('page_ns_0').checked == true)");
|
||
|
ptln("{");
|
||
|
ptln(" if(document.getElementById('nsr_0').checked == true)");
|
||
|
ptln(" {");
|
||
|
ptln(" preview_text = \"".$ID . $this->lang['pm_previewpage']. " \" + document.getElementById('nsr_select').value + (document.getElementById('nsr_select').value==':'? '' : ':') + document.getElementById('pagename').value;");
|
||
|
ptln(" }");
|
||
|
ptln(" else");
|
||
|
ptln(" {");
|
||
|
ptln(" preview_text = \"".$ID . $this->lang['pm_previewpage']. " \" + document.getElementById('newns').value + ':' + document.getElementById('pagename').value;");
|
||
|
ptln(" }");
|
||
|
ptln("}");
|
||
|
ptln("else{");
|
||
|
ptln(" preview_text = \"". sprintf($this->lang['pm_previewns'], $ns). " \" + document.getElementById('ns_select').value + (document.getElementById('ns_select').value==':'? '' : ':') + document.getElementById('namespacename').value;");
|
||
|
ptln("}");
|
||
|
ptln("document.getElementById('preview_output').innerHTML = preview_text;");
|
||
|
ptln("");
|
||
|
ptln("}");
|
||
|
ptln(" </script>");
|
||
|
|
||
|
ptln( '</div>');
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* create a list of namespaces for the html form
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
* @author Arno Puschmann (bin out of _pm_form)
|
||
|
*/
|
||
|
function _pm_form_create_list_ns($ns) {
|
||
|
global $conf;
|
||
|
|
||
|
$namesp = array( 0 => '' ); //Include root
|
||
|
search($namesp, $conf['datadir'], 'search_namespaces', array());
|
||
|
sort($namesp);
|
||
|
foreach($namesp as $row) {
|
||
|
if ( auth_quickaclcheck($row['id'].':*') >= AUTH_CREATE || $row['id'] == $ns ) {
|
||
|
ptln ( ' <option value="'.
|
||
|
($row['id'] ? $row['id'] : ':').
|
||
|
($_REQUEST['ns'] ?
|
||
|
(($row['id'] ? $row['id'] : ":") == $_REQUEST['ns'] ? '" SELECTED>' : '">') :
|
||
|
($row['id'] == $ns ? '" SELECTED>' : '">') ).
|
||
|
($row['id'] ? $row['id'].':' : ": ".$this->lang['pm_root']).
|
||
|
($row['id'] == $ns ? ' '.$this->lang['pm_current'] : '').
|
||
|
"</option>" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* handle user request
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function handle() {
|
||
|
|
||
|
global $conf;
|
||
|
global $lang;
|
||
|
global $ID;
|
||
|
global $INFO;
|
||
|
global $ACT;
|
||
|
|
||
|
// check we have rights to move this document
|
||
|
if( !$INFO['exists'] ) {
|
||
|
$this->have_rights = false;
|
||
|
$this->errors[] = $this->lang['pm_notexist'];
|
||
|
return;
|
||
|
}
|
||
|
// do not move start page
|
||
|
if( $ID == $conf['start'] ) {
|
||
|
$this->have_rights = false;
|
||
|
$this->errors[] = $this->lang['pm_notstart'];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// was a form send?
|
||
|
if (! array_key_exists('page_ns', $_REQUEST)) {
|
||
|
// @fixme do something more intelligent like showing in message
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// extract namespace and document name from ID
|
||
|
$this->opts['ns'] = getNS($ID);
|
||
|
$this->opts['name'] = noNS($ID);
|
||
|
$this->opts['page_ns'] = $_REQUEST['page_ns'];
|
||
|
|
||
|
// check the input for completeness
|
||
|
if( $this->opts['page_ns'] == 'ns' ) {
|
||
|
// @todo Target namespace needn't be new (check pages for overwrite!)
|
||
|
if( $_REQUEST['namespacename'] == '' ) {
|
||
|
$this->errors[] = $this->lang['pm_emptynamespace'];
|
||
|
return;
|
||
|
}
|
||
|
$this->opts['newnsname'] = $_REQUEST['namespacename'];
|
||
|
if ( cleanID($this->opts['newnsname']) == '' ) {
|
||
|
$this->errors[] = $this->lang['pm_badns'];
|
||
|
return;
|
||
|
}
|
||
|
if ($_REQUEST['ns'] == ':') {
|
||
|
$this->opts['newns'] = $this->opts['newnsname'];
|
||
|
}
|
||
|
else {
|
||
|
$this->opts['newns'] = $_REQUEST['ns'].':'.$this->opts['newnsname'];
|
||
|
}
|
||
|
|
||
|
// check the NS if a recursion is needed
|
||
|
// @fixme Is this still needed?
|
||
|
$pagelist = array();
|
||
|
$needrecursion = false;
|
||
|
$nsRelPath = utf8_encodeFN(str_replace(':', '/', $this->opts['ns']));
|
||
|
search($items, $conf['datadir'], 'search_index', '', $nsRelPath);
|
||
|
foreach ($items as $item) {
|
||
|
if ($item['type'] == 'd') {
|
||
|
$needrecursion = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$nsRelPath = utf8_encodeFN(str_replace(':', '/', $this->opts['ns']));
|
||
|
$this->_pm_move_recursive($nsRelPath, $this->opts);
|
||
|
|
||
|
$newNsAbsPath = $conf['datadir'].'/'.str_replace(':', '/', $this->opts['newns']);
|
||
|
$this->_pm_disable_cache($newNsAbsPath);
|
||
|
}
|
||
|
elseif( $this->opts['page_ns'] == 'page' ) {
|
||
|
if( $_REQUEST['pagename'] == '' ) {
|
||
|
$this->errors[] = $this->lang['pm_emptypagename'];
|
||
|
return;
|
||
|
}
|
||
|
$this->opts['newname'] = $_REQUEST['pagename'];
|
||
|
// check that the pagename is valid
|
||
|
if ( cleanID($this->opts['newname']) == '' ) {
|
||
|
$this->errors[] = $this->lang['pm_badname'];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ($_REQUEST['nsr'] == '<old>') {
|
||
|
$this->opts['newns'] = ($_REQUEST['ns_for_page'] == ':' ? '' : $_REQUEST['ns_for_page']);
|
||
|
}
|
||
|
elseif ($_REQUEST['nsr'] =='<new>') {
|
||
|
// if a new namespace was requested, check and use it
|
||
|
if ($_REQUEST['newns'] != '') {
|
||
|
$this->opts['newns'] = $_REQUEST['newns'];
|
||
|
// check that the new namespace is valid
|
||
|
if ( cleanID($this->opts['newns']) == '' ) {
|
||
|
$this->errors[] = $this->lang['pm_badns'];
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
$this->errors[] = $this->lang['pm_badns'];
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
$this->errors[] = $this->lang['pm_fatal'];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$this->_pm_move_page($this->opts);
|
||
|
|
||
|
// @todo if the namespace is now empty, delete it
|
||
|
|
||
|
// Set things up to display the new page.
|
||
|
io_saveFile($conf['cachedir'].'/purgefile', time());
|
||
|
$ID = $opts['new_id'];
|
||
|
$ACT = 'show';
|
||
|
$INFO = pageinfo();
|
||
|
$this->show_form = false;
|
||
|
}
|
||
|
else {
|
||
|
$this->errors[] = $this->lang['pm_fatal'];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
// only go on if no errors occured and inputs are not empty
|
||
|
if (count($this->errors) != 0 ) {
|
||
|
return;
|
||
|
}
|
||
|
// delete empty namespaces if possible
|
||
|
// @fixme does not work like that
|
||
|
foreach ($this->idsToDelete as $idToDelete) {
|
||
|
io_sweepNS($idToDelete);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* touch every file which was moved, because of cached backlinks inside of moved namespace
|
||
|
*
|
||
|
* @author Arno Puschmann 2010-01-29
|
||
|
* @param $pathToSearch
|
||
|
* @return unknown_type
|
||
|
*/
|
||
|
function _pm_disable_cache($pathToSearch) {
|
||
|
$files = scandir($pathToSearch);
|
||
|
if( !empty($files) ) {
|
||
|
foreach($files as $file) {
|
||
|
if( $file == '.' || $file == '..' ) continue;
|
||
|
if( is_dir($pathToSearch.'/'.$file) ) {
|
||
|
$this->_pm_disable_cache($pathToSearch.'/'.$file);
|
||
|
}
|
||
|
else {
|
||
|
if( preg_match('#\.txt$#', $file) ) {
|
||
|
touch($pathToSearch.'/'.$file, time()+1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @author Bastian Wolf
|
||
|
* @param $pathToSearch
|
||
|
* @param $opts
|
||
|
* @return unknown_type
|
||
|
*/
|
||
|
function _pm_move_recursive($pathToSearch, $opts) {
|
||
|
global $ID;
|
||
|
global $conf;
|
||
|
|
||
|
$pagelist = array();
|
||
|
search($pagelist, $conf['datadir'], 'search_index', '', $pathToSearch);
|
||
|
|
||
|
foreach ($pagelist as $page) {
|
||
|
if ($page['type'] == 'd') {
|
||
|
$pathToSearch = utf8_encodeFN(str_replace(':', '/', $page['id']));
|
||
|
// @fixme shouldn't be necessary as ID already exists
|
||
|
io_createNamespace($page['id']);
|
||
|
// NS to move is this one
|
||
|
$nsOpts = $opts;
|
||
|
$nsOpts['ns'] = $page['id'];
|
||
|
// target NS is this folder under the current target NS
|
||
|
$thisFolder = end(explode(':', $page['id']));
|
||
|
$nsOpts['newns'] .= ':'.$thisFolder;
|
||
|
array_push($this->idsToDelete, $page['id']);
|
||
|
// Recursion
|
||
|
$this->_pm_move_recursive($pathToSearch, $nsOpts);
|
||
|
}
|
||
|
elseif ($page['type'] == 'f') {
|
||
|
$ID = $page['id'];
|
||
|
$pageOpts = $opts;
|
||
|
$pageOpts['ns'] = getNS($ID);
|
||
|
$pageOpts['name'] = noNS($ID);
|
||
|
$pageOpts['newname'] = noNS($ID);
|
||
|
$this->_pm_move_page($pageOpts);
|
||
|
}
|
||
|
else {
|
||
|
$this->errors[] = $this->lang['pm_unknown_file_type'];
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* move page
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>, modified by Kay Roesler
|
||
|
*
|
||
|
* @param array $opts
|
||
|
*/
|
||
|
function _pm_move_page($opts) {
|
||
|
|
||
|
global $conf;
|
||
|
global $lang;
|
||
|
global $ID;
|
||
|
global $INFO;
|
||
|
global $ACT;
|
||
|
|
||
|
// Check we have rights to move this document
|
||
|
if ( !$INFO['exists']) {
|
||
|
$this->have_rights = false;
|
||
|
$this->errors[] = $this->lang['pm_notexist'];
|
||
|
return;
|
||
|
}
|
||
|
if ( $ID == $conf['start']) {
|
||
|
$this->have_rights = false;
|
||
|
$this->errors[] = $this->lang['pm_notstart'];
|
||
|
return;
|
||
|
}
|
||
|
if ( auth_quickaclcheck($ID) < AUTH_EDIT ) {
|
||
|
$this->have_rights = false;
|
||
|
$this->errors[] = $this->lang['pm_norights'];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check file is not locked
|
||
|
if (checklock($ID)) {
|
||
|
$this->locked_files[] = $ID;
|
||
|
}
|
||
|
|
||
|
// get all backlink information
|
||
|
$backlinksById = array();
|
||
|
$this->_pm_search($backlinksById, $conf['datadir'], '_pm_search_backlinks', $opts);
|
||
|
|
||
|
// Check we have edit rights on the backlinks and they are not locked
|
||
|
foreach($backlinksById as $backlinkingId=>$backlinks) {
|
||
|
if (auth_quickaclcheck($backlinkingId) < AUTH_EDIT) {
|
||
|
$this->have_rights = false;
|
||
|
}
|
||
|
if (checklock($backlinkingId)) {
|
||
|
$this->locked_files[] = $backlinkingId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Assemble fill document name and path
|
||
|
$opts['new_id'] = cleanID($opts['newns'].':'.$opts['newname']);
|
||
|
$opts['new_path'] = wikiFN($opts['new_id']);
|
||
|
|
||
|
// Has the document name and/or namespace changed?
|
||
|
if ( $opts['newns'] == $opts['ns'] && $opts['newname'] == $opts['name'] ) {
|
||
|
$this->errors[] = $this->lang['pm_nochange'];
|
||
|
return;
|
||
|
}
|
||
|
// Check the page does not already exist
|
||
|
if ( @file_exists($opts['new_path']) ) {
|
||
|
$this->errors[] = sprintf($this->lang['pm_existing'], $opts['newname'],
|
||
|
($opts['newns'] == '' ? $this->lang['pm_root'] : $opts['newns']));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( count($this->errors) != 0 ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* End of init (checks)
|
||
|
*/
|
||
|
|
||
|
// Open the old document and change forward links
|
||
|
lock($ID);
|
||
|
$this->text = io_readFile(wikiFN($ID), True);
|
||
|
|
||
|
// Get an array of forward links from the document
|
||
|
$forward = $this->_pm_getforwardlinks($ID);
|
||
|
|
||
|
// Change the forward links
|
||
|
foreach($forward as $lnk => $lid) {
|
||
|
// Get namespace of target document
|
||
|
$tns = getNS($lid);
|
||
|
$tname = noNS($lid);
|
||
|
// Form new document ID for the target
|
||
|
|
||
|
$matches = array();
|
||
|
if ( $tns == $opts['newns'] ) {
|
||
|
// Document is in same namespace as target
|
||
|
$this->_pm_updatelinks($this->text, array($lnk => $tname));
|
||
|
}
|
||
|
elseif ( preg_match('#^'.$opts['newns'].':(.*:)$#', $tns, $matches) ) {
|
||
|
// Target is in a sub-namespace
|
||
|
$this->_pm_updatelinks($this->text, array($lnk => '.:'.$matches[1].':'.$tname));
|
||
|
}
|
||
|
elseif ( $tns == "" ) {
|
||
|
// Target is in root namespace
|
||
|
$this->_pm_updatelinks($this->text, array($lnk => $lid ));
|
||
|
}
|
||
|
else {
|
||
|
$this->_pm_updatelinks($this->text, array($lnk => $lid ));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( $opts['ns'] != $opts['newns'] ) {
|
||
|
// Change media links when moving between namespaces
|
||
|
$media = $this->_pm_getmedialinks($ID);
|
||
|
foreach($media as $lnk => $lid) {
|
||
|
$tns = getNS($lid);
|
||
|
$tname = noNS($lid);
|
||
|
// Form new document id for the target
|
||
|
$matches = array();
|
||
|
if ( $tns == $opts['newns'] ) {
|
||
|
// Document is in same namespace as target
|
||
|
$this->_pm_updatemedialinks($this->text, $lnk, $tname );
|
||
|
}
|
||
|
elseif ( preg_match('#^'.$opts['newns'].':(.*:)$#', $tns, $matches) ) {
|
||
|
// Target is in a sub-namespace
|
||
|
$this->_pm_updatemedialinks($this->text, $lnk, '.:'.$matches[1].':'.$tname );
|
||
|
}
|
||
|
elseif ( $tns == "" ) {
|
||
|
// Target is in root namespace
|
||
|
$this->_pm_updatemedialinks($this->text, $lnk, ':'.$lid );
|
||
|
}
|
||
|
else {
|
||
|
$this->_pm_updatemedialinks($this->text, $lnk, $lid );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Move the Subscriptions & Indexes
|
||
|
$this->_pm_movemeta('metadir', '/^'.$opts['name'].'\.\w*?$/', $opts);
|
||
|
|
||
|
// Save the updated document in its new location
|
||
|
if ($opts['ns'] == $opts['newns']) {
|
||
|
$lang_key = 'pm_renamed';
|
||
|
}
|
||
|
elseif ( $opts['name'] == $opts['newname'] ) {
|
||
|
$lang_key = 'pm_moved';
|
||
|
}
|
||
|
else {
|
||
|
$lang_key = 'pm_move_rename';
|
||
|
}
|
||
|
$summary = sprintf($this->lang[$lang_key], $ID, $opts['new_id']);
|
||
|
saveWikiText($opts['new_id'], $this->text, $summary);
|
||
|
|
||
|
// Delete the orginal file
|
||
|
if (@file_exists(wikiFN($opts['new_id']))) {
|
||
|
saveWikiText($ID, '', $this->lang['pm_delete'] );
|
||
|
}
|
||
|
|
||
|
// Loop through backlinks
|
||
|
foreach($backlinksById as $backlinkingId => $backlinks) {
|
||
|
$this->_pm_updatebacklinks($backlinkingId, $backlinks, $opts, $brackets);
|
||
|
}
|
||
|
|
||
|
// Move the old revisions
|
||
|
$this->_pm_movemeta('olddir', '/^'.$opts['name'].'\.[0-9]{10}\.txt(\.gz)?$/', $opts);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Modify the links in a backlink.
|
||
|
*
|
||
|
* @param id Page ID of the backlinking page
|
||
|
* @param links Array of page names on this page.
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_updatebacklinks($backlinkingId, $links, $opts, &$brackets) {
|
||
|
global $ID;
|
||
|
|
||
|
// Get namespace of document we are editing
|
||
|
$bns = getNS($backlinkingId);
|
||
|
|
||
|
// Create a clean version of the new name
|
||
|
$cleanname = cleanID($opts['newname']);
|
||
|
|
||
|
// Open backlink
|
||
|
lock($backlinkingId);
|
||
|
$text = io_readFile(wikiFN($backlinkingId),True);
|
||
|
|
||
|
// Form new document ID for this backlink
|
||
|
$matches = array();
|
||
|
// new page is in same namespace as backlink
|
||
|
if ( $bns == $opts['newns'] ) {
|
||
|
$replacementNamespace = '';
|
||
|
}
|
||
|
// new page is in sub-namespace of backlink
|
||
|
elseif ( preg_match('#^'.$bns.':(.*)$#', $opts['newns'], $matches) ) {
|
||
|
$replacementNamespace = '.:'.$matches[1].':';
|
||
|
}
|
||
|
// not same or sub namespace: use absolute reference
|
||
|
else {
|
||
|
$replacementNamespace = $opts['newns'].':';
|
||
|
}
|
||
|
|
||
|
// @fixme stupid: for each page get original backlink and its replacement
|
||
|
$matches = array();
|
||
|
// get an array of: backlinks => replacement
|
||
|
$oid = array();
|
||
|
if ( $bns == $opts['ns'] ) {
|
||
|
// old page was in same namespace as backlink
|
||
|
foreach ( $links as $link ) {
|
||
|
$oid[$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
$oid['.:'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
$oid['.'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
}
|
||
|
}
|
||
|
if ( preg_match('#^'.$bns.':(.*)$#', $opts['ns'], $matches) ) {
|
||
|
// old page was in sub namespace of backlink namespace
|
||
|
foreach ( $links as $link ) {
|
||
|
$oid['.:'.$matches[1].':'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
$oid['.'.$matches[1].':'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
}
|
||
|
}
|
||
|
if ( preg_match('#^'.$opts['ns'].':(.*)$#', $bns , $matches) && $opts['page_ns'] == 'page' ) {
|
||
|
// old page was in upper namespace of backlink
|
||
|
foreach ( $links as $link ) {
|
||
|
$oid['..:'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
$oid['..'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
$oid['.:..:'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
}
|
||
|
}
|
||
|
// replace all other links
|
||
|
foreach ( $links as $link ) {
|
||
|
// absolute links
|
||
|
$oid[$opts['ns'].':'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
//$oid['.:'.$opts['ns'].':'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
|
||
|
// check backwards relative links
|
||
|
$relLink = $link;
|
||
|
$relDots = '..';
|
||
|
$backlinkingNamespaceCount = count(explode(':', $bns));
|
||
|
$oldNamespaces = explode(':', $opts['ns'], $backlinkingNamespaceCount);
|
||
|
$oldNamespaceCount = count($oldNamespaces);
|
||
|
if ($backlinkingNamespaceCount > $oldNamespaceCount) {
|
||
|
$levelDiff = $backlinkingNamespaceCount - $oldNamespaceCount;
|
||
|
for ($i = 0; $i < $levelDiff; $i++) {
|
||
|
$relDots .= ':..';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (array_reverse($oldNamespaces) as $nextUpperNs) {
|
||
|
$relLink = $nextUpperNs.':'.$relLink;
|
||
|
foreach (array($relDots.$relLink, $relDots.':'.$relLink) as $dottedRelLink) {
|
||
|
$absLink=$dottedRelLink;
|
||
|
resolve_pageid($bns, $absLink, $exists);
|
||
|
if ($absLink == $ID) {
|
||
|
$oid[$dottedRelLink] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
}
|
||
|
}
|
||
|
$relDots = '..:'.$relDots;
|
||
|
}
|
||
|
|
||
|
//$oid['..:'.$opts['ns'].':'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
//$oid['..'.$opts['ns'].':'.$link] = $replacementNamespace.(($cleanname == cleanID($link)) ? $link : $opts['newname']);
|
||
|
}
|
||
|
|
||
|
// Make the changes
|
||
|
$this->_pm_updatelinks($text, $oid);
|
||
|
|
||
|
// Save backlink and release lock
|
||
|
saveWikiText($backlinkingId, $text, sprintf($this->lang['pm_linkchange'], $ID, $opts['new_id']));
|
||
|
unlock($backlinkingId);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* modify the links using the pairs in $links
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_updatelinks(&$text, $links) {
|
||
|
foreach( $links as $old => $new ) {
|
||
|
$text = preg_replace( '#\[\[:?' . $old . '((\]\])|[\|\#])#i', '[[' . $new . '\1', $text);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* modify the medialinks from namepspace $old to namespace $new
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_updatemedialinks(&$text, $old, $new) {
|
||
|
// Question marks in media links need some extra handling
|
||
|
$text = preg_replace('#\{\{' . $old . '([\?\|]|(\}\}))#i', '{{' . $new . '\1', $text);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get forward links in a given page which need to be changed.
|
||
|
*
|
||
|
* Not changed: local sections, absolute links
|
||
|
* Changed need to be
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_getforwardlinks($id) {
|
||
|
$data = array();
|
||
|
$text = io_readfile(wikiFN($id));
|
||
|
|
||
|
// match all links
|
||
|
// FIXME may be incorrect because of code blocks
|
||
|
// TODO CamelCase isn't supported, too
|
||
|
preg_match_all('#\[\[(.+?)\]\]#si', $text, $matches, PREG_SET_ORDER);
|
||
|
foreach($matches as $match) {
|
||
|
// ignore local headings [[#some_heading]]
|
||
|
if ( preg_match('/^#/', $match[1])) continue;
|
||
|
|
||
|
// get ID from link and discard most non wikilinks
|
||
|
list($mid) = split('[\|#]', $match[1], 2);
|
||
|
// ignore links with URL schema prefix ([[prefix://]])
|
||
|
if(preg_match('#^\w+://#', $mid)) continue;
|
||
|
// if(preg_match('#^(https?|telnet|gopher|file|wais|ftp|ed2k|irc)://#',$mid)) continue;
|
||
|
// inter-wiki link
|
||
|
if(preg_match('#\w+>#', $mid)) continue;
|
||
|
// baselink ([[/some_link]])
|
||
|
if(preg_match('#^/#', $mid)) continue;
|
||
|
// email addresses
|
||
|
if(strpos($mid, '@') !== FALSE) continue;
|
||
|
// ignore absolute links
|
||
|
if( strpos($mid, ':') === 0 ) continue;
|
||
|
|
||
|
$absoluteMatchId = $mid;
|
||
|
$exists = FALSE;
|
||
|
resolve_pageid(getNS($id), $absoluteMatchId, $exists);
|
||
|
if($absoluteMatchId != FALSE) {
|
||
|
$data[$mid] = $absoluteMatchId;
|
||
|
}
|
||
|
}
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get media links in a given page
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_getmedialinks($id) {
|
||
|
$data = array();
|
||
|
$text = io_readfile(wikiFN($id));
|
||
|
// match all links
|
||
|
// FIXME may be incorrect because of code blocks
|
||
|
// TODO CamelCase isn't supported, too
|
||
|
preg_match_all('#{{(.[^>]+?)}}#si', $text, $matches, PREG_SET_ORDER);
|
||
|
foreach($matches as $match) {
|
||
|
// get ID from link and discard most non wikilinks
|
||
|
list($mid) = split('(\?|\|)', $match[1], 2);
|
||
|
$mns = getNS($mid);
|
||
|
$lnk = $mid;
|
||
|
|
||
|
// namespace starting with "." - prepend current namespace
|
||
|
if(strpos($mns, '.')===0) {
|
||
|
$mid = getNS($id).':'.substr($mid, 1);
|
||
|
}
|
||
|
elseif($mns === FALSE){
|
||
|
// no namespace in link? add current
|
||
|
$mid = getNS($id) . ':' . $mid;
|
||
|
}
|
||
|
$data[$lnk] = preg_replace('#:+#', ':', $mid);
|
||
|
}
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* move meta files (Old Revs, Subscriptions, Meta, etc)
|
||
|
*
|
||
|
* This function meta files between directories
|
||
|
*
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_movemeta($dir, $regex, $opts) {
|
||
|
global $conf;
|
||
|
|
||
|
$old_path = $conf[$dir].'/'.str_replace(':','/',$opts['ns']).'/';
|
||
|
$new_path = $conf[$dir].'/'.str_replace(':','/',$opts['newns']).'/';
|
||
|
$dh = @opendir($old_path);
|
||
|
if($dh) {
|
||
|
while(($file = readdir($dh)) !== false) {
|
||
|
// skip hidden files and upper dirs
|
||
|
if(preg_match('/^\./',$file)) continue;
|
||
|
if(is_file($old_path.$file) and preg_match($regex,$file)) {
|
||
|
io_mkdir_p($new_path);
|
||
|
io_rename($old_path.$file,$new_path.str_replace($opts['name'], $opts['newname'], $file));
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
closedir($dh);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* recurse directory
|
||
|
*
|
||
|
* This function recurses into a given base directory
|
||
|
* and calls the supplied function for each file and directory
|
||
|
*
|
||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||
|
* @param array $data Found data is collected
|
||
|
* @param string $base Directory to be searched in
|
||
|
* @param string $func Name of real search function
|
||
|
* @param array $opts Options to the search functions
|
||
|
* @param string $dir Current relative directory
|
||
|
* @param integer $lvl Level of recursion
|
||
|
*/
|
||
|
function _pm_search(&$data, $base, $func, $opts, $dir='' ,$lvl=1) {
|
||
|
$dirs = array();
|
||
|
$files = array();
|
||
|
|
||
|
// read in directories and files
|
||
|
$dh = @opendir($base.'/'.$dir);
|
||
|
if(!$dh) return;
|
||
|
while(($file = readdir($dh)) !== false) {
|
||
|
// skip hidden files and upper dirs
|
||
|
if(preg_match('/^\./',$file)) continue;
|
||
|
if(is_dir($base.'/'.$dir.'/'.$file)) {
|
||
|
$dirs[] = $dir.'/'.$file;
|
||
|
continue;
|
||
|
}
|
||
|
$files[] = $dir.'/'.$file;
|
||
|
}
|
||
|
closedir($dh);
|
||
|
sort($files);
|
||
|
sort($dirs);
|
||
|
|
||
|
// give directories to userfunction then recurse
|
||
|
foreach($dirs as $dir) {
|
||
|
if ($this->$func($data, $base, $dir, 'd', $lvl, $opts)) {
|
||
|
$this->_pm_search($data, $base, $func, $opts, $dir, $lvl+1);
|
||
|
}
|
||
|
}
|
||
|
// now handle the files
|
||
|
foreach($files as $file) {
|
||
|
$this->$func($data, $base, $file, 'f', $lvl, $opts);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Search for backlinks to a given page
|
||
|
*
|
||
|
* $opts['ns'] namespace of the page
|
||
|
* $opts['name'] name of the page without namespace
|
||
|
*
|
||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||
|
* @author Gary Owen <gary@isection.co.uk>
|
||
|
*/
|
||
|
function _pm_search_backlinks(&$data, $base, $file, $type, $lvl, $opts) {
|
||
|
// we do nothing with directories
|
||
|
if($type == 'd') return true;
|
||
|
// only search txt files
|
||
|
if(!preg_match('#\.txt$#', $file)) return true;
|
||
|
|
||
|
$text = io_readfile($base.'/'.$file);
|
||
|
// absolute search ID
|
||
|
// $absSearchedId = cleanID($opts['ns'].':'.$opts['name']);
|
||
|
$absSearchedId = $opts['name'];
|
||
|
resolve_pageid($opts['ns'], $absSearchedId, $exists);
|
||
|
|
||
|
// construct current namespace
|
||
|
$cid = pathID($file);
|
||
|
$cns = getNS($cid);
|
||
|
|
||
|
// match all links
|
||
|
// FIXME may be incorrect because of code blocks
|
||
|
// FIXME CamelCase isn't supported, too
|
||
|
preg_match_all('#\[\[(.+?)\]\]#si', $text, $matches, PREG_SET_ORDER);
|
||
|
foreach($matches as $match) {
|
||
|
// get ID from link and discard most non wikilinks
|
||
|
list($matchLink) = split('[\|#]', $match[1], 2);
|
||
|
// all URLs with a scheme
|
||
|
if(preg_match('#^\w+://#', $matchLink)) continue;
|
||
|
// if(preg_match('#^(https?|telnet|gopher|file|wais|ftp|ed2k|irc)://#',$matchLink)) continue;
|
||
|
// baselinks
|
||
|
if(preg_match('#^/#', $matchLink)) continue;
|
||
|
// inter-wiki links
|
||
|
if(preg_match('#\w+>#', $matchLink)) continue;
|
||
|
// email addresses
|
||
|
if(strpos($matchLink, '@') !== FALSE) continue;
|
||
|
|
||
|
// get the ID the link refers to by cleaning and resolving it
|
||
|
$matchId = cleanID($matchLink);
|
||
|
resolve_pageid($cns, $matchId, $exists);
|
||
|
$matchPagename = ltrim(noNS($matchId), '.:');
|
||
|
|
||
|
// only collect IDs not in collected $data already
|
||
|
if ($matchId == $absSearchedId // matching link refers to the searched ID
|
||
|
&& (! array_key_exists($cid, $data) // not in $data already
|
||
|
|| empty($data[$cid])
|
||
|
|| ! in_array($matchPagename, $data[$cid]))) {
|
||
|
// @fixme return original link and its replacement
|
||
|
$data[$cid][] = $matchPagename;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|