미디어위키:Gadget-PluginCore.js: 두 판 사이의 차이
((패치 1/2) 사용자 common.js 문서가 없을 때 대응, XSS 취약점 패치) |
편집 요약 없음 |
||
(같은 사용자의 중간 판 9개는 보이지 않습니다) | |||
36번째 줄: | 36번째 줄: | ||
}, | }, | ||
changeDocument: function (title, text, summary) { | changeDocument: function (title, text, summary, otherOptions) { | ||
return new Promise(function (resolve, reject) { | return new Promise(function (resolve, reject) { | ||
repo.api | repo.api | ||
.postWithEditToken({ | .postWithEditToken( | ||
Object.assign( | |||
{ | |||
action: 'edit', | |||
title: title, | |||
text: text, | |||
summary: summary, | |||
//nocreate: true, | |||
}, | |||
otherOptions | |||
) | |||
) | |||
.then( | .then( | ||
function (data) { | function (data) { | ||
resolve(data); | if (data.edit.result === 'Success') resolve(data); | ||
else reject(data); | |||
}, | }, | ||
function (e) { | function (e) { | ||
177번째 줄: | 183번째 줄: | ||
function getPluginCode(plugin) { | function getPluginCode(plugin) { | ||
function entityDecode(doc) { | function entityDecode(doc) { | ||
return $('< | return $('<script></script>').html(doc).text(); | ||
} | } | ||
343번째 줄: | 349번째 줄: | ||
userPlugins: [], | userPlugins: [], | ||
uninstalledPlugins: docPlugins, | uninstalledPlugins: docPlugins, | ||
unupdatedPlugins: [] | unupdatedPlugins: [], | ||
} | }; | ||
} else throw err; | } else throw err; | ||
} | } | ||
) | ) | ||
.then(function(results) { | .then(function (results) { | ||
var commonjsRaw = results.commonjsRaw; | var commonjsRaw = results.commonjsRaw; | ||
var userPlugins = results.userPlugins; | var userPlugins = results.userPlugins; | ||
407번째 줄: | 413번째 줄: | ||
// 플러그인 설치 버튼 클릭 이벤트 핸들러 | // 플러그인 설치 버튼 클릭 이벤트 핸들러 | ||
var setInstallPage = function (pluginMeta) { | var setInstallPage = function (pluginMeta) { | ||
function install( | |||
commonjsPath, | |||
commonjs, | |||
needPluginNames, | |||
otherOptions | |||
) { | |||
return repo | |||
.changeDocument( | |||
commonjsPath, | |||
commonjs, | |||
'플러그인 ' + needPluginNames + '설치', | |||
otherOptions | |||
) | |||
.then(function () { | |||
location.reload(); | |||
}) | |||
.catch(function (e) { | |||
if (typeof e === 'object' && e.edit.captcha) { | |||
var captchaId = e.edit.captcha.id; | |||
var captchaAnswer = prompt( | |||
'플러그인을 설치하려면 다음 문제를 해결하십시오: \n' + | |||
e.edit.captcha.question | |||
); | |||
if(captchaAnswer === null) { // 사용자 취소 시 | |||
$('.install-button').text('설치하기'); | |||
return; | |||
} | |||
return install( | |||
commonjsPath, | |||
commonjs, | |||
needPluginNames, | |||
{ | |||
captchaid: captchaId, | |||
captchaword: captchaAnswer, | |||
} | |||
); | |||
} | |||
alert( | |||
'플러그인 설치에 실패했습니다. 다시 시도해주세요.' | |||
); | |||
console.error(e); | |||
$('.install-button').text('설치하기'); | |||
}); | |||
} | |||
return installPagePromise.then(function ($installPage) { | return installPagePromise.then(function ($installPage) { | ||
$installPage | $installPage | ||
412번째 줄: | 467번째 줄: | ||
.on('click', function () { | .on('click', function () { | ||
$('.install-button').text('설치중..'); | $('.install-button').text('설치중..'); | ||
$('.install-button').off('click'); | //$('.install-button').off('click'); | ||
var commonjs = getUpdatedCommonjs( | var commonjs = getUpdatedCommonjs( | ||
419번째 줄: | 474번째 줄: | ||
pluginMeta | pluginMeta | ||
); | ); | ||
install(commonjsPath, commonjs, needPluginNames); | |||
}); | }); | ||
443번째 줄: | 486번째 줄: | ||
// 플러그인 설치 필요 틀이 있는 페이지인 경우 | // 플러그인 설치 필요 틀이 있는 페이지인 경우 | ||
if ($loadCodeLink.length >= 1) { | if ($loadCodeLink.length >= 1) { | ||
$loadCodeLink.eq(0).closest(' | $loadCodeLink.eq(0).closest('.ambox').show(); // 플러그인 설치 필요 틀을 보여줍니다. | ||
$('.plugin-name').eq(0).text(needPluginNames); // 필요한 플러그인들의 이름을 표시합니다. | $('.plugin-name').eq(0).text(needPluginNames); // 필요한 플러그인들의 이름을 표시합니다. | ||
$loadCodeLink.on('click', function () { | $loadCodeLink.on('click', function () { |
2024년 11월 21일 (목) 03:36 기준 최신판
/** [[틀:플러그인]]을 사용 가능하게 해 줍니다. 사용자의 허락을 맡고 사용자의 commonjs 편집을 허가 할 수 있는 문서입니다.
* 작성자: [[사용자:BANIP|BANIP]]
* - [[백괴게임:관리자 요청/2018년 1월]]에서 BANIP님 요청으로 퍼왔습니다.
* - 2023.06.04 2.0.0 전체적인 소스 리팩토링/MediaWikiAPI의 의존성 삭제 --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 6월 4일 (일) 13:52 (KST)
*/
// 페이지 로드 후 실행
$(function () {
// 구현필요
var repo = {
PAGE_MISSING: 'The page does not exist.',
api: new mw.Api(),
getRawDocument: function (title) {
return new Promise(function (resolve, reject) {
repo.api
.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: title,
formatversion: 2,
})
.then(
function (data) {
var page = data.query.pages[0];
if (page.missing) {
reject(repo.PAGE_MISSING);
} else {
resolve(page.revisions[0].content);
}
},
function (e) {
reject(e);
}
);
});
},
changeDocument: function (title, text, summary, otherOptions) {
return new Promise(function (resolve, reject) {
repo.api
.postWithEditToken(
Object.assign(
{
action: 'edit',
title: title,
text: text,
summary: summary,
//nocreate: true,
},
otherOptions
)
)
.then(
function (data) {
if (data.edit.result === 'Success') resolve(data);
else reject(data);
},
function (e) {
reject(e);
}
);
});
},
getDocument: function (title) {
return new Promise(function (resolve, reject) {
repo.api
.get({
action: 'parse',
page: title,
formatversion: 2,
})
.then(
function (data) {
resolve(data.parse.text);
},
function (e) {
reject(e);
}
);
});
},
getPluginMetadata: function (page) {
var revisionPromise = repo.api.get({
action: 'query',
prop: 'revisions',
rvlimit: 1,
rvprop: 'timestamp|user|comment',
titles: page,
formatversion: 2,
});
var parsedPagePromise = repo.api.get({
action: 'parse',
page: page,
formatversion: 2,
});
return Promise.all([revisionPromise, parsedPagePromise]).then(function (
results
) {
var revisionResponse = results[0];
var parsedPageResponse = results[1];
var pages = revisionResponse.query.pages;
var pageId = Object.keys(pages)[0];
var revision = pages[pageId].revisions[0];
var parsedContent = parsedPageResponse.parse.text;
return {
author: revision.user,
timestamp: revision.timestamp,
comment: revision.comment,
code: $(parsedContent).find('pre.script').text(),
};
});
},
};
// 플러그인이 비어있는지 확인
function isPluginsEmpty(plugins) {
return Object.keys(plugins).length === 0;
}
//플러그인의 모든 키 순회
function forEach(object, callback) {
for (var key in object) {
var variable = object[key];
callback(variable, key);
}
}
var getPlugin = {
// 문서에서 사용하는 플러그인들을 체크합니다. use-script클래스를 가진 모든 돔 요소를 조사하고
// 그 돔에 내장된 플러그인 데이터를 docPlugins에 추가합니다.
document: function () {
// 현재 문서에 사용되는 플러그인들의 데이터를 docPlugins에 추가합니다.
return $('.use-script')
.toArray()
.map(function (el) {
var $this = $(el);
return {
name: $this.attr('data-name'), // 플러그인 이름
descript: $this.attr('data-descript'), // 플러그인 내용
version: $this.attr('data-version'), // 플러그인 이름
local: $this.attr('data-local') === 'true', // 즉시 실행 여부
creat: $this.attr('data-creat'), // 플러그인 작성자 이름
state: $this.attr('data-state'),
link: $this.attr('data-link'),
executable: $this.attr('data-executable') === 'true',
};
});
},
user: function (commonjs) {
var userplugins = [];
// plugins.---가 있는지 체크하는 정규식
var pluginreg = /JSON \=\> ([\S]+) = (\{.*\})/g;
var nameMatch = pluginreg.exec(commonjs);
while (nameMatch) {
userplugins.push(JSON.parse(nameMatch[2]));
nameMatch = pluginreg.exec(commonjs);
}
return userplugins;
},
};
// 문서의 서브타이틀을 플러그인들의 이름으로 설정합니다.
function setSubtitleByPlugins(plugins) {
// 서브타이틀에 현재 가동중인 플러그인들의 이름을 추가합니다.
var pluginNames = plugins.map(function (plugin) {
return plugin.name;
});
var subTitle = ' ' + pluginNames.join(', ') + ' 플러그인 가동중';
$('#siteSub').text(function (i, v) {
return v + subTitle;
});
}
// 플러그인을 사용자문서에 추가합니다.
function getUpdatedCommonjs(plugins, commonjs, pluginMeta) {
function getPluginCode(plugin) {
function entityDecode(doc) {
return $('<script></script>').html(doc).text();
}
function getDocHead(plugin) {
var docHead = '';
var toJSONPlugin = Object.assign({}, plugin);
toJSONPlugin.code = undefined;
toJSONPlugin.link = undefined;
docHead += '\n';
docHead +=
'\n/** 플러그인 ' +
plugin.name +
'***************************\n';
docHead += '* ' + plugin.descript + '\n';
docHead += '* 버전 => ' + plugin.version + '\n';
docHead +=
'* 작성자 : [[사용자:' +
plugin.creat +
'|' +
plugin.creat +
']] \n';
docHead +=
'* JSON => ' +
plugin.name +
' = ' +
JSON.stringify(toJSONPlugin) +
'; \n';
docHead += '*/ \n';
docHead += 'function plugin_' + plugin.name + '(){\n';
if (plugin.local)
docHead +=
' if($("[data-name=\'' +
plugin.name +
'\']").length >= 1){\n';
return docHead;
}
function getDocFoot(plugin) {
var docFoot = '';
if (plugin.local) docFoot += '\n }\n';
docFoot += '\n}\n';
if (plugin.executable)
docFoot += '$( plugin_' + plugin.name + ' );\n';
docFoot += '/* ' + plugin.name + ' 끝 */\n\n';
return docFoot;
}
var docHead = getDocHead(plugin);
var docFoot = getDocFoot(plugin);
return entityDecode(docHead + pluginMeta[plugin.name].code + docFoot);
}
//commonjs에서 특정 플러그인 제거
function removePluginByDoc(pluginTitle, commonjs) {
var reg = new RegExp(
'\\/\\*\\* 플러그인 ' +
pluginTitle +
'([\\s\\S]*)\\/\\* ' +
pluginTitle +
' 끝 \\*\\/',
'g'
);
commonjs = commonjs.replace(reg, '');
return commonjs;
}
plugins.forEach(function (plugin) {
commonjs = removePluginByDoc(plugin.name, commonjs);
commonjs += getPluginCode(plugin);
});
return commonjs;
}
// 플러그인 안내 템플릿을 반환합니다.
function getInstallPagePromise(
userPlugins,
uninstalledPlugins,
unupdatedPlugins,
pluginMeta
) {
function getPluginCard($template, plugin, status) {
var pluginName = plugin.name;
var $box = $template
.find('.cloneable.p-box')
.clone()
.removeClass('cloneable');
var code = pluginMeta[pluginName].code;
$box.find('.p-status').text(status);
$box.find('.p-code').text(code.replace(/\s{1,}$/, ''));
$box.find('.p-name').text(pluginName);
$box.find('.p-descript').text(plugin.descript);
if (status == '버전업') {
var prevVersion = userPlugins.find(function (userPlugin) {
return userPlugin.name == pluginName;
}).version;
$box.find('.p-version').text(prevVersion + ' => ' + plugin.version);
} else {
$box.find('.p-version').text(plugin.version);
}
$box.find('.p-local').text(
plugin.local == true ? '일부 문서만' : '문서 전체'
);
$box.find('.p-creat').text(pluginMeta[pluginName].author);
$box.find('.p-last').text(pluginMeta[pluginName].timestamp);
$box.find('.p-comment').text(pluginMeta[pluginName].comment);
return $box;
}
return repo.getDocument('틀:플러그인/setup').then(function (templateRaw) {
var $template = $(templateRaw);
var allPlugins = uninstalledPlugins.concat(unupdatedPlugins);
allPlugins.forEach(function (plugin) {
var status = uninstalledPlugins.includes(plugin) ? '설치' : '버전업';
var pluginCard = getPluginCard($template, plugin, status);
$template.find('.box-article').append(pluginCard);
});
return $template;
});
}
// 플러그인 설치 관련 로직
(function () {
var commonjsPath = '사용자:' + mw.config.get('wgUserName') + '/common.js'; // 사용자의 commonjs의 경로를 획득합니다.
var docPlugins = getPlugin.document(); // 문서에서 사용하는 플러그인들을 체크합니다.
if (Object.keys(docPlugins).length === 0) return; // 문서에서 사용하는 플러그인이 없으면 종료합니다.
setSubtitleByPlugins(docPlugins); // 문서의 서브타이틀을 문서에서 사용중인 플러그인들의 이름으로 설정합니다.
repo.getRawDocument(commonjsPath) // 사용자의 commonjs의 문서를 획득합니다.
.then(
function (commonjsRaw) {
var userPlugins = getPlugin.user(commonjsRaw); // 사용자가 가지고 있는 플러그인들을 체크합니다.
var uninstalledPlugins = docPlugins.filter(function (plugin) {
return !userPlugins.some(function (userPlugin) {
return userPlugin.name === plugin.name;
});
}); // 사용자가 가지고 있지 않은 플러그인들을 체크합니다.
var unupdatedPlugins = docPlugins.filter(function (plugin) {
return userPlugins.some(function (userPlugin) {
return (
userPlugin.name === plugin.name &&
userPlugin.version !== plugin.version
);
});
}); // 사용자가 가지고 있는 플러그인들 중 버전이 다른 플러그인들을 체크합니다.
return {
commonjsRaw: commonjsRaw,
userPlugins: userPlugins,
uninstalledPlugins: uninstalledPlugins,
unupdatedPlugins: unupdatedPlugins,
};
},
function (err) {
// 사용자 common.js 문서가 없는 경우
if (err === repo.PAGE_MISSING) {
// 모든 플러그인을 설치합니다.
return {
commonjsRaw: '',
userPlugins: [],
uninstalledPlugins: docPlugins,
unupdatedPlugins: [],
};
} else throw err;
}
)
.then(function (results) {
var commonjsRaw = results.commonjsRaw;
var userPlugins = results.userPlugins;
var uninstalledPlugins = results.uninstalledPlugins;
var unupdatedPlugins = results.unupdatedPlugins;
// 추가 플러그인 설치가 필요하지 않은 경우 로직 종료
if (
isPluginsEmpty(uninstalledPlugins) &&
isPluginsEmpty(unupdatedPlugins)
)
return;
// 설치가 필요한 플러그인들의 이름을 문자열로 반환합니다.
var needPluginNames = uninstalledPlugins
.concat(unupdatedPlugins)
.map(function (plugin) {
return plugin.name;
})
.join(', ');
// 플러그인 개발자/코드/마지막 수정일 등 획득, 설치버튼 클릭 시 즉시 설치 가능하도록 미리 Promise를 생성
var pluginMetaPromise = Promise.all(
uninstalledPlugins
.concat(unupdatedPlugins)
.map(function (plugin) {
return Promise.all([
plugin.name,
repo.getPluginMetadata(plugin.state),
]);
})
)
.then(function (pluginMeta) {
return Object.fromEntries(pluginMeta);
})
.catch(function () {
alert(
'플러그인 정보를 불러오는데 실패했습니다. 페이지를 리로드 해주세요.'
);
});
// 플러그인 리스트 수집 끝나면 플러그인 안내 템플릿을 반환하는 Promise를 생성
var installPagePromise = pluginMetaPromise
.then(function (pluginMeta) {
return getInstallPagePromise(
userPlugins,
uninstalledPlugins,
unupdatedPlugins,
pluginMeta
);
})
.catch(function () {
alert(
'플러그인 설치 페이지를 불러오는데 실패했습니다. 페이지를 리로드 해주세요.'
);
});
// 플러그인 설치 버튼 클릭 이벤트 핸들러
var setInstallPage = function (pluginMeta) {
function install(
commonjsPath,
commonjs,
needPluginNames,
otherOptions
) {
return repo
.changeDocument(
commonjsPath,
commonjs,
'플러그인 ' + needPluginNames + '설치',
otherOptions
)
.then(function () {
location.reload();
})
.catch(function (e) {
if (typeof e === 'object' && e.edit.captcha) {
var captchaId = e.edit.captcha.id;
var captchaAnswer = prompt(
'플러그인을 설치하려면 다음 문제를 해결하십시오: \n' +
e.edit.captcha.question
);
if(captchaAnswer === null) { // 사용자 취소 시
$('.install-button').text('설치하기');
return;
}
return install(
commonjsPath,
commonjs,
needPluginNames,
{
captchaid: captchaId,
captchaword: captchaAnswer,
}
);
}
alert(
'플러그인 설치에 실패했습니다. 다시 시도해주세요.'
);
console.error(e);
$('.install-button').text('설치하기');
});
}
return installPagePromise.then(function ($installPage) {
$installPage
.find('.install-button')
.on('click', function () {
$('.install-button').text('설치중..');
//$('.install-button').off('click');
var commonjs = getUpdatedCommonjs(
uninstalledPlugins.concat(unupdatedPlugins),
commonjsRaw,
pluginMeta
);
install(commonjsPath, commonjs, needPluginNames);
});
var $document = $('#mw-content-text');
$document.html($installPage);
});
};
var $loadCodeLink = $('.plugin-install');
// 플러그인 설치 필요 틀이 있는 페이지인 경우
if ($loadCodeLink.length >= 1) {
$loadCodeLink.eq(0).closest('.ambox').show(); // 플러그인 설치 필요 틀을 보여줍니다.
$('.plugin-name').eq(0).text(needPluginNames); // 필요한 플러그인들의 이름을 표시합니다.
$loadCodeLink.on('click', function () {
// 클릭한 버튼 이벤트 끄고 로딩중메세지 표시
$loadCodeLink.off('click');
$loadCodeLink.text(
'필요한 플러그인 데이터를 가져오고 있습니다...'
);
pluginMetaPromise.then(setInstallPage);
}); // 플러그인 설치 버튼을 클릭하면 플러그인 체크 로직을 실행합니다.
} else {
pluginMetaPromise.then(setInstallPage);
}
});
})();
});