Index: trunk/extensions/RecordAdmin/SpecialRecordAdmin.18n.php |
— | — | @@ -1,72 +0,0 @@ |
2 | | -<?php |
3 | | -/*Internationalisation message file of Record Admin extension |
4 | | -* Created by Bertrand GRONDIN |
5 | | -* |
6 | | -* @addtogroup Extensions |
7 | | -* |
8 | | -*/ |
9 | | -$messages=array(); |
10 | | - |
11 | | -$messages['en'] = array ( |
12 | | - 'recordadmin' => 'Record Administration', |
13 | | - 'recordadmin-desc' => 'A special page for finding and editing record pages using a form', |
14 | | - 'recordadmin-select' => 'Select the type of record to search for ', |
15 | | - 'recordadmin-newsearch' => 'New $1 search', |
16 | | - 'recordadmin-newrecord' => 'Select another record type', |
17 | | - 'recordadmin-submit' => 'Submit', |
18 | | - 'recordadmin-create' => 'Find or Create a "$1" record', |
19 | | - 'recordadmin-alreadyexist' => 'Sorry, "$1" already exists!', |
20 | | - 'recordadmin-createsuccess' => '$1 created successfully', |
21 | | - 'recordadmin-createerror' => 'An error occurred while attempting to create the $1!', |
22 | | - 'recordadmin-badtitle' => 'Bad title!', |
23 | | - 'recordadmin-recordid' => 'Record ID:', |
24 | | - 'recordadmin-invert' => 'Invert selection', |
25 | | - 'recordadmin-buttonsearch' => 'Search', |
26 | | - 'recordadmin-buttoncreate' => 'Create', |
27 | | - 'recordadmin-buttonreset' => 'Reset', |
28 | | - 'recordadmin-searchresult' => 'Search results', |
29 | | - 'recordadmin-nomatch' => 'No matching records found!', |
30 | | - 'recordadmin-edit' => 'Editing $1', |
31 | | - 'recordadmin-typeupdated' => '$1 properties updated', |
32 | | - 'recordadmin-updatesuccess' => '$1 updated successfully', |
33 | | - 'recordadmin-updateerror' => 'An error occurred during update', |
34 | | - 'recordadmin-buttonsave' => 'Save', |
35 | | - 'recordadmin-noform' => 'There is no form associated with "$1" records!', |
36 | | - 'recordadmin-createlink' => 'click <a href=$1>here</a> to create one', |
37 | | - 'recordadmin-newcreated' => 'New $1 created from public form', |
38 | | - 'recordadmin-summary-typecreated' => 'New $1 created', |
39 | | - 'recordadmin-viewlink' => '(<a href="$1">view</a>)', |
40 | | - 'recordadmin-editlink' => '<a href="$1").">edit</a>', |
41 | | -); |
42 | | - |
43 | | -$messages['fr'] = array ( |
44 | | - 'recordadmin' => 'Gestion des enregistrements', |
45 | | - 'recordadmin-desc' => 'Une page spéciale pour trouver et modifier l’enregistrement des pages par l’utilisation d’un formulaire', |
46 | | - 'recordadmin-select' => 'Sélectionner le type d’enregistrement à rechercher pour', |
47 | | - 'recordadmin-newsearch' => 'Nouvelle recherche $1', |
48 | | - 'recordadmin-newrecord' => 'Sélectionner un autre type d’enregistrement', |
49 | | - 'recordadmin-submit' => 'Soumettre', |
50 | | - 'recordadmin-create' => 'Chercher ou créer un enregistrement « $1 »', |
51 | | - 'recordadmin-alreadyexist' => 'Désolé, « $1 » existe déjà !', |
52 | | - 'recordadmin-createsuccess' => '$1 creé avec succès', |
53 | | - 'recordadmin-createerror' => 'Une erreur est intervenue lors de la tentative de création de $1 !', |
54 | | - 'recordadmin-badtitle' => 'Mauvais titre!', |
55 | | - 'recordadmin-recordid' => 'Enregistrement ID :', |
56 | | - 'recordadmin-invert' => 'Inverser la sélection', |
57 | | - 'recordadmin-buttonsearch' => 'Rechercher', |
58 | | - 'recordadmin-buttoncreate' => 'Créer', |
59 | | - 'recordadmin-buttonreset' => 'Réinitialiser', |
60 | | - 'recordadmin-searchresult' => 'Résultats de la recherche', |
61 | | - 'recordadmin-nomatch' => 'Aucun enregistrement correspondant de trouvé !', |
62 | | - 'recordadmin-edit' => 'Modifier $1', |
63 | | - 'recordadmin-typeupdated' => 'propriété de $1 mises à jour', |
64 | | - 'recordadmin-updatesuccess' => '$1 mis à jour avec succès', |
65 | | - 'recordadmin-updateerror' => 'Une erreur a été rencontrée lors de la mise à jour', |
66 | | - 'recordadmin-buttonsave' => 'Sauvegarder', |
67 | | - 'recordadmin-noform' => 'Il n’y a aucun formulaire avec l’enregistrement « $1 » !', |
68 | | - 'recordadmin-createlink' => 'cliquez <a href=$1>ici</a> pour en créer un', |
69 | | - 'recordadmin-newcreated' => 'Nouveau $1 créé à partir d’un formulaire public', |
70 | | - 'recordadmin-summary-typecreated' => 'Nouveau $1 de créer', |
71 | | - 'recordadmin-viewlink' => 'voir', |
72 | | - 'recordadmin-editlink' => 'modifier', |
73 | | -); |
Index: trunk/extensions/RecordAdmin/SpecialRecordAdmin.php |
— | — | @@ -1,477 +0,0 @@ |
2 | | -<?php |
3 | | -/** |
4 | | - * Extension:RecordAdmin - MediaWiki extension |
5 | | - *{{Category:Extensions|RecordAdmin}}{{php}}{{Category:Extensions created with Template:SpecialPage}} |
6 | | - * @package MediaWiki |
7 | | - * @subpackage Extensions |
8 | | - * @author Aran Dunkley [http://www.organicdesign.co.nz/nad User:Nad] |
9 | | - * @licence GNU General Public Licence 2.0 or later |
10 | | - */ |
11 | | - |
12 | | -if (!defined('MEDIAWIKI')) die('Not an entry point.'); |
13 | | - |
14 | | -define('RECORDADMIN_VERSION','0.2.1, 2008-10-31'); |
15 | | - |
16 | | -$wgRecordAdminCategory = 'Records'; # Category which contains the templates used as records and having corresponding forms |
17 | | -$wgRecordAdminUseNamespaces = false; # Whether record articles should be in a namespace of the same name as their type |
18 | | -$dir = dirname(__FILE__) . '/'; |
19 | | -$wgExtensionMessagesFiles['RecordAdmin'] = $dir . 'SpecialRecordAdmin.18n.php'; |
20 | | -$wgExtensionAliasesFiles['RecordAdmin'] = $dir . 'RecordAdmin.alias.php'; |
21 | | -$wgExtensionFunctions[] = 'wfSetupRecordAdmin'; |
22 | | - |
23 | | -$wgExtensionCredits['specialpage'][] = array( |
24 | | - 'name' => 'Record administration', |
25 | | - 'author' => '[http://www.organicdesign.co.nz/nad User:Nad], Bertrand GRONDIN', |
26 | | - 'description' => 'A special page for finding and editing record articles using a form', |
27 | | - 'descriptionmsg' => 'recordadmin-desc', |
28 | | - 'url' => 'http://www.organicdesign.co.nz/Extension:SpecialExample', |
29 | | - 'version' => RECORDADMIN_VERSION |
30 | | -); |
31 | | - |
32 | | -require_once "$IP/includes/SpecialPage.php"; |
33 | | - |
34 | | -/** |
35 | | - * Define a new class based on the SpecialPage class |
36 | | - */ |
37 | | -class SpecialRecordAdmin extends SpecialPage { |
38 | | - |
39 | | - var $form = ''; |
40 | | - var $types = array(); |
41 | | - var $guid = ''; |
42 | | - |
43 | | - function __construct() { |
44 | | - # Name to use for creating a new record either via RecordAdmin or a public form |
45 | | - # todo: should add a hook here for custom default-naming |
46 | | - $this->guid = strftime('%Y%m%d', time()).'-'.substr(strtoupper(uniqid()), -5); |
47 | | - |
48 | | - SpecialPage::SpecialPage( |
49 | | - 'RecordAdmin', # name as seen in links etc |
50 | | - 'sysop', # user rights required |
51 | | - true, # listed in special:specialpages |
52 | | - false, # function called by execute() - defaults to wfSpecial{$name} |
53 | | - false, # file included by execute() - defaults to Special{$name}.php, only used if no function |
54 | | - false # includable |
55 | | - ); |
56 | | - } |
57 | | - |
58 | | - /** |
59 | | - * Override SpecialPage::execute() |
60 | | - */ |
61 | | - function execute($param) { |
62 | | - global $wgOut, $wgRequest, $wgRecordAdminCategory, $wgRecordAdminUseNamespaces; |
63 | | - wfLoadExtensionMessages ('RecordAdmin'); |
64 | | - $this->setHeaders(); |
65 | | - $type = $wgRequest->getText('wpType') or $type = $param; |
66 | | - $record = $wgRequest->getText('wpRecord'); |
67 | | - $invert = $wgRequest->getText('wpInvert'); |
68 | | - $title = Title::makeTitle(NS_SPECIAL, 'RecordAdmin'); |
69 | | - $wpTitle = trim($wgRequest->getText('wpTitle')); |
70 | | - |
71 | | - if ($type && $wgRecordAdminUseNamespaces) { |
72 | | - if ($wpTitle && !ereg("^$type:.+$", $wpTitle)) $wpTitle = "$type:$wpTitle"; |
73 | | - } |
74 | | - |
75 | | - $wgOut->addHTML("<div class='center'><a href='".$title->getLocalURL()."/$type'>".wfMsg('recordadmin-newsearch', $type)."</a> | " |
76 | | - . "<a href='".$title->getLocalURL()."'>".wfMsg('recordadmin-newrecord')."</a></div><br>\n" |
77 | | - ); |
78 | | - |
79 | | - # Get posted form values if any |
80 | | - $posted = array(); |
81 | | - foreach ($_POST as $k => $v) if (ereg('^ra_(.+)$', $k, $m)) $posted[$m[1]] = $v; |
82 | | - |
83 | | - # Read in and prepare the form for this record type if one has been selected |
84 | | - if ($type) $this->preProcessForm($type); |
85 | | - |
86 | | - # Extract the input names and types used in the form |
87 | | - $this->examineForm(); |
88 | | - |
89 | | - # Clear any default values |
90 | | - $this->populateForm(array()); |
91 | | - |
92 | | - # If no type selected, render select list of record types from Category:Records |
93 | | - if (empty($type)) { |
94 | | - $wgOut->addWikiText("==".wfMsg('recordadmin-select')."==\n"); |
95 | | - |
96 | | - # Get titles in Category:Records and build option list |
97 | | - $options = ''; |
98 | | - $dbr = &wfGetDB(DB_SLAVE); |
99 | | - $cl = $dbr->tableName('categorylinks'); |
100 | | - $cat = $dbr->addQuotes($wgRecordAdminCategory); |
101 | | - $res = $dbr->select($cl, 'cl_from', "cl_to = $cat", __METHOD__, array('ORDER BY' => 'cl_sortkey')); |
102 | | - while ($row = $dbr->fetchRow($res)) $options .= '<option>'.Title::newFromID($row[0])->getText().'</option>'; |
103 | | - |
104 | | - # Render type-selecting form |
105 | | - $wgOut->addHTML(wfElement('form', array('action' => $title->getLocalURL('action=submit'), 'method' => 'post'), null) |
106 | | - . "<select name='wpType'>$options</select> " |
107 | | - . wfElement('input', array('type' => 'submit', 'value' => wfMsg('recordadmin-submit'))) |
108 | | - . '</form>' |
109 | | - ); |
110 | | - } |
111 | | - |
112 | | - # Record type known, but no record selected, render form for searching or creating |
113 | | - elseif (empty($record)) { |
114 | | - $wgOut->addWikiText("==".wfMsg('recordadmin-create', $type)."==\n"); |
115 | | - |
116 | | - # Process Create submission |
117 | | - if (count($posted) && $wgRequest->getText('wpCreate')) { |
118 | | - if (empty($wpTitle)) { |
119 | | - $wpTitle = $this->guid; |
120 | | - if ($wgRecordAdminUseNamespaces) $wpTitle = "$type:$wpTitle"; |
121 | | - } |
122 | | - $t = Title::newFromText($wpTitle); |
123 | | - if (is_object($t)) { |
124 | | - if ($t->exists()) $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-alreadyexist' ,$wpTitle)."</div>\n"); |
125 | | - else { |
126 | | - |
127 | | - # Attempt to create the article |
128 | | - $article = new Article($t); |
129 | | - $summary = "[[Special:RecordAdmin/$type|RecordAdmin]]:".wfMsg('recordadmin-summary-typecreated'); |
130 | | - $text = ''; |
131 | | - foreach ($posted as $k => $v) if ($v) { |
132 | | - if ($this->types[$k] == 'bool') $v = 'yes'; |
133 | | - $text .= "| $k = $v\n"; |
134 | | - } |
135 | | - $text = $text ? "{{"."$type\n$text}}" : "{{"."$type}}"; |
136 | | - $success = $article->doEdit($text, $summary, EDIT_NEW); |
137 | | - |
138 | | - # Report success or error |
139 | | - if ($success) $wgOut->addHTML("<div class='successbox'>".wfMsg('recordadmin-createsuccess',$wpTitle)."</div>\n"); |
140 | | - else $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-createerror', $type)."</div>\n"); |
141 | | - } |
142 | | - } else $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-badtitle')."</div>\n"); |
143 | | - $wgOut->addHTML("<br><br><br><br>\n"); |
144 | | - } |
145 | | - |
146 | | - # Populate the search form with any posted values |
147 | | - $this->populateForm($posted); |
148 | | - |
149 | | - # Render the form |
150 | | - $wgOut->addHTML( |
151 | | - wfElement('form', array('class' => 'recordadmin', 'action' => $title->getLocalURL('action=submit'), 'method' => 'post'), null) |
152 | | - .'<b>'.wfMsg('recordadmin-recordid').'</b> '.wfElement('input', array('name' => 'wpTitle', 'size' => 30, 'value' => $wpTitle)) |
153 | | - .' '.wfElement('input', array('name' => 'wpInvert', 'type' => 'checkbox')).' '.wfMsg('recordadmin-invert') |
154 | | - ."\n<br><br><hr><br>\n{$this->form}" |
155 | | - .wfElement('input', array('type' => 'hidden', 'name' => 'wpType', 'value' => $type)) |
156 | | - .'<br><hr><br><table width="100%"><tr>' |
157 | | - .'<td>'.wfElement('input', array('type' => 'submit', 'name' => 'wpFind', 'value' => wfMsg('recordadmin-buttonsearch'))).'</td>' |
158 | | - .'<td>'.wfElement('input', array('type' => 'submit', 'name' => 'wpCreate', 'value' => wfMsg('recordadmin-buttoncreate'))).'</td>' |
159 | | - .'<td width="100%" align="left">'.wfElement('input', array('type' => 'reset', 'value' => wfMsg('recordadmin-buttonreset'))).'</td>' |
160 | | - .'</tr></table></form>' |
161 | | - ); |
162 | | - |
163 | | - # Process Find submission |
164 | | - if (count($posted) && $wgRequest->getText('wpFind')) { |
165 | | - $wgOut->addWikiText("<br>\n== ".wfMsg('recordadmin-searchresult')." ==\n"); |
166 | | - |
167 | | - # Select records which use the template and exhibit a matching title and other fields |
168 | | - $records = array(); |
169 | | - $dbr = &wfGetDB(DB_SLAVE); |
170 | | - $tbl = $dbr->tableName('templatelinks'); |
171 | | - $ty = $dbr->addQuotes($type); |
172 | | - $res = $dbr->select($tbl, 'tl_from', "tl_namespace = 10 AND tl_title = $ty", __METHOD__); |
173 | | - while ($row = $dbr->fetchRow($res)) { |
174 | | - $t = Title::newFromID($row[0]); |
175 | | - if (empty($wpTitle) || eregi($wpTitle, $t->getPrefixedText())) { |
176 | | - $a = new Article($t); |
177 | | - $text = $a->getContent(); |
178 | | - $match = true; |
179 | | - $r = array($t); |
180 | | - foreach (array_keys($this->types) as $k) { |
181 | | - $v = isset($posted[$k]) ? ($this->types[$k] == 'bool' ? 'yes' : $posted[$k]) : ''; |
182 | | - $i = preg_match("|\s*\|\s*$k\s*=\s*(.*?)\s*(?=[\|\}])|si", $text, $m); |
183 | | - if ($v && !($i && eregi($v, $m[1]))) $match = false; |
184 | | - $r[$k] = isset($m[1]) ? $m[1] : ''; |
185 | | - } |
186 | | - if ($invert) $match = !$match; |
187 | | - if ($match) $records[$t->getPrefixedText()] = $r; |
188 | | - } |
189 | | - } |
190 | | - $dbr->freeResult($res); |
191 | | - |
192 | | - # Render search results |
193 | | - if (count($records)) { |
194 | | - |
195 | | - # Pass1, scan the records to find the create date of each and sort by that |
196 | | - $sorted = array(); |
197 | | - foreach ($records as $k => $r) { |
198 | | - $t = $r[0]; |
199 | | - $id = $t->getArticleID(); |
200 | | - $r[1] = $k; |
201 | | - $tbl = $dbr->tableName('revision'); |
202 | | - $row = $dbr->selectRow( |
203 | | - $tbl, |
204 | | - 'rev_timestamp', |
205 | | - "rev_page = $id", |
206 | | - __METHOD__, |
207 | | - array('ORDER BY' => 'rev_timestamp') |
208 | | - ); |
209 | | - $sorted[$row->rev_timestamp] = $r; |
210 | | - } |
211 | | - krsort($sorted); |
212 | | - |
213 | | - $table = "<table class='sortable recordadmin $type-record'>\n<tr> |
214 | | - <th class='col1'>$type<br></th><th class='col2'>Created<br></th>"; |
215 | | - foreach (array_keys($this->types) as $k) $table .= "<th class='col$k'>$k<br></th>"; |
216 | | - $table .= "</tr>\n"; |
217 | | - $stripe = ''; |
218 | | - foreach ($sorted as $ts => $r) { |
219 | | - $ts = preg_replace('|^..(..)(..)(..)(..)(..)..$|', '$3/$2/$1 $4:$5', $ts); |
220 | | - $t = $r[0]; |
221 | | - $k = $r[1]; |
222 | | - $stripe = $stripe ? '' : ' class="stripe"'; |
223 | | - $table .= "<tr$stripe><td class='col1'>(<a href='".$t->getLocalURL()."'>".wfMsg('recordadmin-viewlink')."</a>)"; |
224 | | - $table .= "(<a href='".$title->getLocalURL("wpType=$type&wpRecord=$k")."'>".wfMsg('recordadmin-editlink')."</a>)</td>\n"; |
225 | | - $table .= "<td class='col2'>$ts</td>\n"; |
226 | | - $i = 0; |
227 | | - foreach (array_keys($this->types) as $k) { |
228 | | - $v = isset($r[$k]) ? $r[$k] : ' '; |
229 | | - $table .= "<td class='col$k'>$v</td>"; |
230 | | - } |
231 | | - $table .= "</tr>\n"; |
232 | | - } |
233 | | - $table .= "</table>\n"; |
234 | | - $wgOut->addHTML($table); |
235 | | - } else $wgOut->addWikiText(wfMsg('recordadmin-nomatch')."\n"); |
236 | | - } |
237 | | - } |
238 | | - |
239 | | - # A specific record has been selected, render form for updating |
240 | | - else { |
241 | | - $wgOut->addWikiText("== ".wfMsg('recordadmin-edit',$record)." ==\n"); |
242 | | - $article = new Article(Title::newFromText($record)); |
243 | | - $text = $article->fetchContent(); |
244 | | - |
245 | | - # Update article if form posted |
246 | | - if (count($posted)) { |
247 | | - |
248 | | - # Get the location and length of the record braces to replace |
249 | | - foreach ($this->examineBraces($text) as $brace) if ($brace['NAME'] == $type) $braces = $brace; |
250 | | - |
251 | | - # Attempt to save the article |
252 | | - $summary = "[[Special:RecordAdmin/$type|RecordAdmin]]: ".wfMsg('recordadmin-typeupdated',$type); |
253 | | - $replace = ''; |
254 | | - foreach ($posted as $k => $v) if ($v) { |
255 | | - if ($this->types[$k] == 'bool') $v = 'yes'; |
256 | | - $replace .= "| $k = $v\n"; |
257 | | - } |
258 | | - $replace = $replace ? "{{"."$type\n$replace}}" : "{{"."$type}}"; |
259 | | - $text = substr_replace($text, $replace, $braces['OFFSET'], $braces['LENGTH']); |
260 | | - $success = $article->doEdit($text, $summary, EDIT_UPDATE); |
261 | | - |
262 | | - # Report success or error |
263 | | - if ($success) $wgOut->addHTML("<div class='successbox'>".wfMsg('recordadmin-updatesuccess',$type)."</div>\n"); |
264 | | - else $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-updateerror')."</div>\n"); |
265 | | - $wgOut->addHTML("<br><br><br><br>\n"); |
266 | | - } |
267 | | - |
268 | | - # Populate the form with the current values in the article |
269 | | - foreach ($this->examineBraces($text) as $brace) if ($brace['NAME'] == $type) $braces = $brace; |
270 | | - $this->populateForm(substr($text, $braces['OFFSET'], $braces['LENGTH'])); |
271 | | - |
272 | | - # Render the form |
273 | | - $wgOut->addHTML(wfElement('form', array('class' => 'recordadmin', 'action' => $title->getLocalURL('action=submit'), 'method' => 'post'), null)); |
274 | | - $wgOut->addHTML($this->form); |
275 | | - $wgOut->addHTML(wfElement('input', array('type' => 'hidden', 'name' => 'wpType', 'value' => $type))); |
276 | | - $wgOut->addHTML(wfElement('input', array('type' => 'hidden', 'name' => 'wpRecord', 'value' => $record))); |
277 | | - $wgOut->addHTML('<br><hr><br><table width="100%"><tr>' |
278 | | - .'<td>'.wfElement('input', array('type' => 'submit', 'value' => wfMsg('recordadmin-buttonsave'))).'</td>' |
279 | | - .'<td width="100%" align="left">'.wfElement('input', array('type' => 'reset', 'value' => wfMsg('recordadmin-buttonreset'))).'</td>' |
280 | | - .'</tr></table></form>' |
281 | | - ); |
282 | | - } |
283 | | - } |
284 | | - |
285 | | - /** |
286 | | - * Read in and prepare the form (for use as a search filter) for passed record type |
287 | | - * - we're using the record's own form as a filter for searching for records |
288 | | - * - extract only the content from between the form tags and remove any submit inputs |
289 | | - */ |
290 | | - function preProcessForm($type) { |
291 | | - $title = Title::newFromText($type, NS_FORM); |
292 | | - if ($title->exists()) { |
293 | | - $form = new Article($title); |
294 | | - $form = $form->getContent(); |
295 | | - $form = preg_replace('#<input.+?type=[\'"]?submit["\']?.+?/(input| *)>#', '', $form); # remove submits |
296 | | - $form = preg_replace('#^.+?<form.+?>#s', '', $form); # remove up to and including form open |
297 | | - $form = preg_replace('#</form>.+?$#s', '', $form); # remove form close and everything after |
298 | | - $form = preg_replace('#name\s*=\s*([\'"])(.*?)\\1#s', 'name="ra_$2"', $form); # prefix input names with ra_ |
299 | | - $form = preg_replace('#(<select.+?>)\s*(?!<option/>)#s', '$1<option selected/>', $form); # ensure all select lists have default blank |
300 | | - } |
301 | | - |
302 | | - # Create a red link to the form if it doesn't exist |
303 | | - else { |
304 | | - $form = "<b>".wfMsg('recordadmin-noform',$type)."</b>" |
305 | | - ."<br><br>".wfMsg('recordadmin-createlink',$title->getLocalURL('action=edit') )."</div>"; |
306 | | - } |
307 | | - $this->form = $form; |
308 | | - } |
309 | | - |
310 | | - |
311 | | - /** |
312 | | - * Populates the form values from the passed values |
313 | | - * - $form is HTML text |
314 | | - * - $values may be a hash or wikitext template syntax |
315 | | - */ |
316 | | - function populateForm($values) { |
317 | | - |
318 | | - # If values are wikitext, convert to hash |
319 | | - if (!is_array($values)) { |
320 | | - $text = $values; |
321 | | - $values = array(); |
322 | | - preg_match_all("|\|\s*(.+?)\s*=\s*(.*?)\s*(?=[\|\}])|s", $text, $m); |
323 | | - foreach ($m[1] as $i => $k) $values[$k] = $m[2][$i]; |
324 | | - } |
325 | | - |
326 | | - # Add the values into the form's HTML depending on their type |
327 | | - foreach($this->types as $k => $type) { |
328 | | - |
329 | | - # Get this input element's html text and position and length |
330 | | - preg_match("|<([a-zA-Z]+)[^<]+?name=\"ra_$k\".*?>(.*?</\\1>)?|s", $this->form, $m, PREG_OFFSET_CAPTURE); |
331 | | - list($html, $pos) = $m[0]; |
332 | | - $len = strlen($html); |
333 | | - |
334 | | - # Modify the element according to its type |
335 | | - # - clears default value, then adds new value |
336 | | - $v = isset($values[$k]) ? $values[$k] : ''; |
337 | | - switch ($type) { |
338 | | - case 'text': |
339 | | - $html = preg_replace("|value\s*=\s*\".*?\"|", "", $html); |
340 | | - if ($v) $html = preg_replace("|(/?>)$|", " value=\"$v\" $1", $html); |
341 | | - break; |
342 | | - case 'bool': |
343 | | - $html = preg_replace("|checked|", "", $html); |
344 | | - if ($v) $html = preg_replace("|(/?>)$|", " checked $1", $html); |
345 | | - break; |
346 | | - case 'list': |
347 | | - $html = preg_replace("|(<option[^<>]*) selected|", "$1", $html); |
348 | | - if ($v) $html = preg_replace("|(?<=<option)(?=>$v</option>)|s", " selected", $html); |
349 | | - break; |
350 | | - case 'blob': |
351 | | - $html = preg_replace("|>.*?(?=</textarea>)|s", ">$v", $html); |
352 | | - break; |
353 | | - } |
354 | | - |
355 | | - # Replace the element in the form with the modified html |
356 | | - $this->form = substr_replace($this->form, $html, $pos, $len); |
357 | | - } |
358 | | - } |
359 | | - |
360 | | - /** |
361 | | - * Returns an array of types used by the passed HTML text form |
362 | | - * - supported types, text, select, checkbox, textarea |
363 | | - */ |
364 | | - function examineForm() { |
365 | | - $this->types = array(); |
366 | | - preg_match_all("|<([a-zA-Z]+)[^<]+?name=\"ra_(.+?)\".*?>|", $this->form, $m); |
367 | | - foreach ($m[2] as $i => $k) { |
368 | | - $tag = $m[1][$i]; |
369 | | - $type = preg_match("|type\s*=\s*\"(.+?)\"|", $m[0][$i], $n) ? $n[1] : ''; |
370 | | - switch ($tag) { |
371 | | - case 'input': |
372 | | - switch ($type) { |
373 | | - case 'checkbox': |
374 | | - $this->types[$k] = 'bool'; |
375 | | - break; |
376 | | - default: |
377 | | - $this->types[$k] = 'text'; |
378 | | - break; |
379 | | - } |
380 | | - break; |
381 | | - case 'select': |
382 | | - $this->types[$k] = 'list'; |
383 | | - break; |
384 | | - case 'textarea': |
385 | | - $this->types[$k] = 'blob'; |
386 | | - break; |
387 | | - } |
388 | | - } |
389 | | - } |
390 | | - |
391 | | - /** |
392 | | - * Return array of braces used and the name, position, length and depth |
393 | | - * See http://www.organicdesign.co.nz/MediaWiki_code_snippets |
394 | | - */ |
395 | | - function examineBraces(&$content) { |
396 | | - $braces = array(); |
397 | | - $depths = array(); |
398 | | - $depth = 1; |
399 | | - $index = 0; |
400 | | - while (preg_match('/\\{\\{\\s*([#a-z0-9_]*)|\\}\\}/is', $content, $match, PREG_OFFSET_CAPTURE, $index)) { |
401 | | - $index = $match[0][1]+2; |
402 | | - if ($match[0][0] == '}}') { |
403 | | - $brace =& $braces[$depths[$depth-1]]; |
404 | | - $brace['LENGTH'] = $match[0][1]-$brace['OFFSET']+2; |
405 | | - $brace['DEPTH'] = $depth--; |
406 | | - } |
407 | | - else { |
408 | | - $depths[$depth++] = count($braces); |
409 | | - $braces[] = array( |
410 | | - 'NAME' => $match[1][0], |
411 | | - 'OFFSET' => $match[0][1] |
412 | | - ); |
413 | | - } |
414 | | - } |
415 | | - return $braces; |
416 | | - } |
417 | | - |
418 | | - /** |
419 | | - * A callback for processing public forms |
420 | | - */ |
421 | | - function createRecord() { |
422 | | - global $wgRequest, $wgRecordAdminUseNamespaces; |
423 | | - $type = $wgRequest->getText('wpType'); |
424 | | - $title = $wgRequest->getText('wpTitle'); |
425 | | - |
426 | | - # Get types in this kind of record from form |
427 | | - $this->preProcessForm($type); |
428 | | - $this->examineForm(); |
429 | | - |
430 | | - # Use guid if no title specified |
431 | | - if (empty($title)) { |
432 | | - $title = $this->guid; |
433 | | - if ($wgRecordAdminUseNamespaces) $title = "$type:$title"; |
434 | | - } |
435 | | - |
436 | | - # Attempt to create the article |
437 | | - $title = Title::newFromText($title); |
438 | | - if (is_object($title) && !$title->exists()) { |
439 | | - $article = new Article($title); |
440 | | - $summary = wfMsg('recordadmin-newcreated'); |
441 | | - $text = ''; |
442 | | - foreach ($_POST as $k => $v) if ($v && isset($this->types[$k])) { |
443 | | - if ($this->types[$k] == 'bool') $v = 'yes'; |
444 | | - $text .= "| $k = $v\n"; |
445 | | - } |
446 | | - $text = $text ? "{{"."$type\n$text}}" : "{{"."$type}}"; |
447 | | - $success = $article->doEdit($text, $summary, EDIT_NEW); |
448 | | - } |
449 | | - } |
450 | | - |
451 | | - # If a record was created by a public form, make last 5 digits of ID available via a tag |
452 | | - function expandTag($text, $argv, &$parser) { |
453 | | - $parser->mOutput->mCacheTime = -1; |
454 | | - return $this->guid ? substr($this->guid, -5) : ''; |
455 | | - } |
456 | | - |
457 | | -} |
458 | | - |
459 | | -/** |
460 | | - * Called from $wgExtensionFunctions array when initialising extensions |
461 | | - */ |
462 | | -function wfSetupRecordAdmin() { |
463 | | - global $wgSpecialRecordAdmin, $wgParser, $wgLanguageCode, $wgMessageCache, $wgRequest; |
464 | | - |
465 | | - # Make a global singleton so methods are accessible as callbacks etc |
466 | | - $wgSpecialRecordAdmin = new SpecialRecordAdmin(); |
467 | | - |
468 | | - # Make recordID's of articles created with public forms available via recordid tag |
469 | | - $wgParser->setHook('recordid', array($wgSpecialRecordAdmin, 'expandTag')); |
470 | | - |
471 | | - # Check if posting a public creation form |
472 | | - $title = Title::newFromText($wgRequest->getText('title')); |
473 | | - if (is_object($title) && $title->getNamespace() != NS_SPECIAL && $wgRequest->getText('wpType') && $wgRequest->getText('wpCreate')) |
474 | | - $wgSpecialRecordAdmin->createRecord(); |
475 | | - |
476 | | - # Add the specialpage to the environment |
477 | | - SpecialPage::addPage($wgSpecialRecordAdmin); |
478 | | -} |
Index: trunk/extensions/RecordAdmin/RecordAdmin.18n.php |
— | — | @@ -0,0 +1,82 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Internationalisation for RecordAdmin extension |
| 5 | + * |
| 6 | + * @author Bertrand GRONDIN |
| 7 | + * @file |
| 8 | + * @ingroup Extensions |
| 9 | + */ |
| 10 | + |
| 11 | +$messages = array(); |
| 12 | + |
| 13 | +/** English |
| 14 | + * @author Nad |
| 15 | + * @author Bertrand GRONDIN |
| 16 | + */ |
| 17 | +$messages['en'] = array( |
| 18 | + 'recordadmin' => 'Record administration', |
| 19 | + 'recordadmin-desc' => 'A [[Special:RecordAdmin|special page]] for finding and editing record pages using a form', |
| 20 | + 'recordadmin-category' => 'Records', |
| 21 | + 'recordadmin-select' => 'Select the type of record to search for ', |
| 22 | + 'recordadmin-newsearch' => 'New $1 search', |
| 23 | + 'recordadmin-newrecord' => 'Select another record type', |
| 24 | + 'recordadmin-submit' => 'Submit', |
| 25 | + 'recordadmin-create' => 'Find or create a "$1" record', |
| 26 | + 'recordadmin-alreadyexist' => 'Sorry, "$1" already exists!', |
| 27 | + 'recordadmin-createsuccess' => '$1 created successfully', |
| 28 | + 'recordadmin-createerror' => 'An error occurred while attempting to create the $1!', |
| 29 | + 'recordadmin-badtitle' => 'Bad title!', |
| 30 | + 'recordadmin-recordid' => 'Record ID:', |
| 31 | + 'recordadmin-invert' => 'Invert selection', |
| 32 | + 'recordadmin-buttonsearch' => 'Search', |
| 33 | + 'recordadmin-buttoncreate' => 'Create', |
| 34 | + 'recordadmin-buttonreset' => 'Reset', |
| 35 | + 'recordadmin-searchresult' => 'Search results', |
| 36 | + 'recordadmin-nomatch' => 'No matching records found!', |
| 37 | + 'recordadmin-edit' => 'Editing $1', |
| 38 | + 'recordadmin-typeupdated' => '$1 properties updated', |
| 39 | + 'recordadmin-updatesuccess' => '$1 updated successfully', |
| 40 | + 'recordadmin-updateerror' => 'An error occurred during update', |
| 41 | + 'recordadmin-buttonsave' => 'Save', |
| 42 | + 'recordadmin-noform' => 'There is no form associated with "$1" records!', |
| 43 | + 'recordadmin-createlink' => '<a href=$1>create one</a>', |
| 44 | + 'recordadmin-newcreated' => 'New $1 created from public form', |
| 45 | + 'recordadmin-summary-typecreated' => 'New $1 created', |
| 46 | + 'recordadmin-viewlink' => 'view', |
| 47 | + 'recordadmin-editlink' => 'edit', |
| 48 | +); |
| 49 | + |
| 50 | +$messages['qqq'] = array ( |
| 51 | + 'recordadmin-category' => 'Category which contains the templates used as records and having corresponding forms', |
| 52 | + |
| 53 | +$messages['fr'] = array ( |
| 54 | + 'recordadmin' => 'Gestion des enregistrements', |
| 55 | + 'recordadmin-desc' => 'Une page spéciale pour trouver et modifier l’enregistrement des pages par l’utilisation d’un formulaire', |
| 56 | + 'recordadmin-select' => 'Sélectionner le type d’enregistrement à rechercher pour', |
| 57 | + 'recordadmin-newsearch' => 'Nouvelle recherche $1', |
| 58 | + 'recordadmin-newrecord' => 'Sélectionner un autre type d’enregistrement', |
| 59 | + 'recordadmin-submit' => 'Soumettre', |
| 60 | + 'recordadmin-create' => 'Chercher ou créer un enregistrement « $1 »', |
| 61 | + 'recordadmin-alreadyexist' => 'Désolé, « $1 » existe déjà !', |
| 62 | + 'recordadmin-createsuccess' => '$1 creé avec succès', |
| 63 | + 'recordadmin-createerror' => 'Une erreur est intervenue lors de la tentative de création de $1 !', |
| 64 | + 'recordadmin-badtitle' => 'Mauvais titre!', |
| 65 | + 'recordadmin-recordid' => 'Enregistrement ID :', |
| 66 | + 'recordadmin-invert' => 'Inverser la sélection', |
| 67 | + 'recordadmin-buttonsearch' => 'Rechercher', |
| 68 | + 'recordadmin-buttoncreate' => 'Créer', |
| 69 | + 'recordadmin-buttonreset' => 'Réinitialiser', |
| 70 | + 'recordadmin-searchresult' => 'Résultats de la recherche', |
| 71 | + 'recordadmin-nomatch' => 'Aucun enregistrement correspondant de trouvé !', |
| 72 | + 'recordadmin-edit' => 'Modifier $1', |
| 73 | + 'recordadmin-typeupdated' => 'propriété de $1 mises à jour', |
| 74 | + 'recordadmin-updatesuccess' => '$1 mis à jour avec succès', |
| 75 | + 'recordadmin-updateerror' => 'Une erreur a été rencontrée lors de la mise à jour', |
| 76 | + 'recordadmin-buttonsave' => 'Sauvegarder', |
| 77 | + 'recordadmin-noform' => 'Il n’y a aucun formulaire avec l’enregistrement « $1 » !', |
| 78 | + 'recordadmin-createlink' => 'cliquez <a href=$1>ici</a> pour en créer un', |
| 79 | + 'recordadmin-newcreated' => 'Nouveau $1 créé à partir d’un formulaire public', |
| 80 | + 'recordadmin-summary-typecreated' => 'Nouveau $1 de créer', |
| 81 | + 'recordadmin-viewlink' => 'voir', |
| 82 | + 'recordadmin-editlink' => 'modifier', |
| 83 | +); |
Property changes on: trunk/extensions/RecordAdmin/RecordAdmin.18n.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
Added: svn:eol-style |
1 | 84 | + native |
Index: trunk/extensions/RecordAdmin/RecordAdmin.php |
— | — | @@ -0,0 +1,479 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Extension:RecordAdmin - MediaWiki extension |
| 5 | + * {{Category:Extensions|RecordAdmin}}{{php}}{{Category:Extensions created with Template:SpecialPage}} |
| 6 | + * @package MediaWiki |
| 7 | + * @subpackage Extensions |
| 8 | + * @author Aran Dunkley [http://www.organicdesign.co.nz/nad User:Nad] |
| 9 | + * @author Bertrand GRONDIN |
| 10 | + * @licence GNU General Public Licence 2.0 or later |
| 11 | + */ |
| 12 | + |
| 13 | +if (!defined('MEDIAWIKI')) die('Not an entry point.'); |
| 14 | + |
| 15 | +define('RECORDADMIN_VERSION','0.3, 2008-10-31'); |
| 16 | + |
| 17 | +$wgRecordAdminUseNamespaces = false; # Whether record articles should be in a namespace of the same name as their type |
| 18 | +$dir = dirname(__FILE__) . '/'; |
| 19 | +$wgExtensionMessagesFiles['RecordAdmin'] = $dir . 'RecordAdmin.18n.php'; |
| 20 | +$wgExtensionAliasesFiles['RecordAdmin'] = $dir . 'RecordAdmin.alias.php'; |
| 21 | +$wgExtensionFunctions[] = 'wfSetupRecordAdmin'; |
| 22 | + |
| 23 | +$wgExtensionCredits['specialpage'][] = array( |
| 24 | + 'name' => 'Record administration', |
| 25 | + 'author' => array( '[http://www.organicdesign.co.nz/nad User:Nad]', 'Bertrand GRONDIN' ), |
| 26 | + 'description' => 'A special page for finding and editing record articles using a form', |
| 27 | + 'descriptionmsg' => 'recordadmin-desc', |
| 28 | + 'url' => 'http://www.organicdesign.co.nz/Extension:SpecialExample', |
| 29 | + 'version' => RECORDADMIN_VERSION, |
| 30 | +); |
| 31 | + |
| 32 | +require_once "$IP/includes/SpecialPage.php"; |
| 33 | + |
| 34 | +/** |
| 35 | + * Define a new class based on the SpecialPage class |
| 36 | + */ |
| 37 | +class SpecialRecordAdmin extends SpecialPage { |
| 38 | + |
| 39 | + var $form = ''; |
| 40 | + var $types = array(); |
| 41 | + var $guid = ''; |
| 42 | + |
| 43 | + function __construct() { |
| 44 | + # Name to use for creating a new record either via RecordAdmin or a public form |
| 45 | + # todo: should add a hook here for custom default-naming |
| 46 | + $this->guid = strftime('%Y%m%d', time()).'-'.substr(strtoupper(uniqid()), -5); |
| 47 | + |
| 48 | + SpecialPage::SpecialPage( |
| 49 | + 'RecordAdmin', # name as seen in links etc |
| 50 | + 'sysop', # user rights required |
| 51 | + true, # listed in special:specialpages |
| 52 | + false, # function called by execute() - defaults to wfSpecial{$name} |
| 53 | + false, # file included by execute() - defaults to Special{$name}.php, only used if no function |
| 54 | + false # includable |
| 55 | + ); |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * Override SpecialPage::execute() |
| 60 | + */ |
| 61 | + function execute($param) { |
| 62 | + global $wgOut, $wgRequest, $wgRecordAdminUseNamespaces; |
| 63 | + wfLoadExtensionMessages ('RecordAdmin'); |
| 64 | + $this->setHeaders(); |
| 65 | + $type = $wgRequest->getText('wpType') or $type = $param; |
| 66 | + $record = $wgRequest->getText('wpRecord'); |
| 67 | + $invert = $wgRequest->getText('wpInvert'); |
| 68 | + $title = Title::makeTitle(NS_SPECIAL, 'RecordAdmin'); |
| 69 | + $wpTitle = trim($wgRequest->getText('wpTitle')); |
| 70 | + |
| 71 | + if ($type && $wgRecordAdminUseNamespaces) { |
| 72 | + if ($wpTitle && !ereg("^$type:.+$", $wpTitle)) $wpTitle = "$type:$wpTitle"; |
| 73 | + } |
| 74 | + |
| 75 | + $wgOut->addHTML("<div class='center'><a href='".$title->getLocalURL()."/$type'>".wfMsg('recordadmin-newsearch', $type)."</a> | " |
| 76 | + . "<a href='".$title->getLocalURL()."'>".wfMsg('recordadmin-newrecord')."</a></div><br>\n" |
| 77 | + ); |
| 78 | + |
| 79 | + # Get posted form values if any |
| 80 | + $posted = array(); |
| 81 | + foreach ($_POST as $k => $v) if (ereg('^ra_(.+)$', $k, $m)) $posted[$m[1]] = $v; |
| 82 | + |
| 83 | + # Read in and prepare the form for this record type if one has been selected |
| 84 | + if ($type) $this->preProcessForm($type); |
| 85 | + |
| 86 | + # Extract the input names and types used in the form |
| 87 | + $this->examineForm(); |
| 88 | + |
| 89 | + # Clear any default values |
| 90 | + $this->populateForm(array()); |
| 91 | + |
| 92 | + # If no type selected, render select list of record types from Category:Records |
| 93 | + if (empty($type)) { |
| 94 | + $wgOut->addWikiText("==".wfMsg('recordadmin-select')."==\n"); |
| 95 | + |
| 96 | + # Get titles in 'recordadmin-category' (default: Category:Records) and build option list |
| 97 | + $options = ''; |
| 98 | + $dbr = &wfGetDB(DB_SLAVE); |
| 99 | + $cl = $dbr->tableName('categorylinks'); |
| 100 | + $cat = $dbr->addQuotes( wfMsgForContent( 'recordadmin-category' ) ); |
| 101 | + $res = $dbr->select($cl, 'cl_from', "cl_to = $cat", __METHOD__, array('ORDER BY' => 'cl_sortkey')); |
| 102 | + while ($row = $dbr->fetchRow($res)) $options .= '<option>'.Title::newFromID($row[0])->getText().'</option>'; |
| 103 | + |
| 104 | + # Render type-selecting form |
| 105 | + $wgOut->addHTML(wfElement('form', array('action' => $title->getLocalURL('action=submit'), 'method' => 'post'), null) |
| 106 | + . "<select name='wpType'>$options</select> " |
| 107 | + . wfElement('input', array('type' => 'submit', 'value' => wfMsg('recordadmin-submit'))) |
| 108 | + . '</form>' |
| 109 | + ); |
| 110 | + } |
| 111 | + |
| 112 | + # Record type known, but no record selected, render form for searching or creating |
| 113 | + elseif (empty($record)) { |
| 114 | + $wgOut->addWikiText("==".wfMsg('recordadmin-create', $type)."==\n"); |
| 115 | + |
| 116 | + # Process Create submission |
| 117 | + if (count($posted) && $wgRequest->getText('wpCreate')) { |
| 118 | + if (empty($wpTitle)) { |
| 119 | + $wpTitle = $this->guid; |
| 120 | + if ($wgRecordAdminUseNamespaces) $wpTitle = "$type:$wpTitle"; |
| 121 | + } |
| 122 | + $t = Title::newFromText($wpTitle); |
| 123 | + if (is_object($t)) { |
| 124 | + if ($t->exists()) $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-alreadyexist' ,$wpTitle)."</div>\n"); |
| 125 | + else { |
| 126 | + |
| 127 | + # Attempt to create the article |
| 128 | + $article = new Article($t); |
| 129 | + $summary = "[[Special:RecordAdmin/$type|RecordAdmin]]:".wfMsg('recordadmin-summary-typecreated'); |
| 130 | + $text = ''; |
| 131 | + foreach ($posted as $k => $v) if ($v) { |
| 132 | + if ($this->types[$k] == 'bool') $v = 'yes'; |
| 133 | + $text .= "| $k = $v\n"; |
| 134 | + } |
| 135 | + $text = $text ? "{{"."$type\n$text}}" : "{{"."$type}}"; |
| 136 | + $success = $article->doEdit($text, $summary, EDIT_NEW); |
| 137 | + |
| 138 | + # Report success or error |
| 139 | + if ($success) $wgOut->addHTML("<div class='successbox'>".wfMsg('recordadmin-createsuccess',$wpTitle)."</div>\n"); |
| 140 | + else $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-createerror', $type)."</div>\n"); |
| 141 | + } |
| 142 | + } else $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-badtitle')."</div>\n"); |
| 143 | + $wgOut->addHTML("<br><br><br><br>\n"); |
| 144 | + } |
| 145 | + |
| 146 | + # Populate the search form with any posted values |
| 147 | + $this->populateForm($posted); |
| 148 | + |
| 149 | + # Render the form |
| 150 | + $wgOut->addHTML( |
| 151 | + wfElement('form', array('class' => 'recordadmin', 'action' => $title->getLocalURL('action=submit'), 'method' => 'post'), null) |
| 152 | + .'<b>'.wfMsg('recordadmin-recordid').'</b> '.wfElement('input', array('name' => 'wpTitle', 'size' => 30, 'value' => $wpTitle)) |
| 153 | + .' '.wfElement('input', array('name' => 'wpInvert', 'type' => 'checkbox')).' '.wfMsg('recordadmin-invert') |
| 154 | + ."\n<br><br><hr><br>\n{$this->form}" |
| 155 | + .wfElement('input', array('type' => 'hidden', 'name' => 'wpType', 'value' => $type)) |
| 156 | + .'<br><hr><br><table width="100%"><tr>' |
| 157 | + .'<td>'.wfElement('input', array('type' => 'submit', 'name' => 'wpFind', 'value' => wfMsg('recordadmin-buttonsearch'))).'</td>' |
| 158 | + .'<td>'.wfElement('input', array('type' => 'submit', 'name' => 'wpCreate', 'value' => wfMsg('recordadmin-buttoncreate'))).'</td>' |
| 159 | + .'<td width="100%" align="left">'.wfElement('input', array('type' => 'reset', 'value' => wfMsg('recordadmin-buttonreset'))).'</td>' |
| 160 | + .'</tr></table></form>' |
| 161 | + ); |
| 162 | + |
| 163 | + # Process Find submission |
| 164 | + if (count($posted) && $wgRequest->getText('wpFind')) { |
| 165 | + $wgOut->addWikiText("<br>\n== ".wfMsg('recordadmin-searchresult')." ==\n"); |
| 166 | + |
| 167 | + # Select records which use the template and exhibit a matching title and other fields |
| 168 | + $records = array(); |
| 169 | + $dbr = &wfGetDB(DB_SLAVE); |
| 170 | + $tbl = $dbr->tableName('templatelinks'); |
| 171 | + $ty = $dbr->addQuotes($type); |
| 172 | + $res = $dbr->select($tbl, 'tl_from', "tl_namespace = 10 AND tl_title = $ty", __METHOD__); |
| 173 | + while ($row = $dbr->fetchRow($res)) { |
| 174 | + $t = Title::newFromID($row[0]); |
| 175 | + if (empty($wpTitle) || eregi($wpTitle, $t->getPrefixedText())) { |
| 176 | + $a = new Article($t); |
| 177 | + $text = $a->getContent(); |
| 178 | + $match = true; |
| 179 | + $r = array($t); |
| 180 | + foreach (array_keys($this->types) as $k) { |
| 181 | + $v = isset($posted[$k]) ? ($this->types[$k] == 'bool' ? 'yes' : $posted[$k]) : ''; |
| 182 | + $i = preg_match("|\s*\|\s*$k\s*=\s*(.*?)\s*(?=[\|\}])|si", $text, $m); |
| 183 | + if ($v && !($i && eregi($v, $m[1]))) $match = false; |
| 184 | + $r[$k] = isset($m[1]) ? $m[1] : ''; |
| 185 | + } |
| 186 | + if ($invert) $match = !$match; |
| 187 | + if ($match) $records[$t->getPrefixedText()] = $r; |
| 188 | + } |
| 189 | + } |
| 190 | + $dbr->freeResult($res); |
| 191 | + |
| 192 | + # Render search results |
| 193 | + if (count($records)) { |
| 194 | + |
| 195 | + # Pass1, scan the records to find the create date of each and sort by that |
| 196 | + $sorted = array(); |
| 197 | + foreach ($records as $k => $r) { |
| 198 | + $t = $r[0]; |
| 199 | + $id = $t->getArticleID(); |
| 200 | + $r[1] = $k; |
| 201 | + $tbl = $dbr->tableName('revision'); |
| 202 | + $row = $dbr->selectRow( |
| 203 | + $tbl, |
| 204 | + 'rev_timestamp', |
| 205 | + "rev_page = $id", |
| 206 | + __METHOD__, |
| 207 | + array('ORDER BY' => 'rev_timestamp') |
| 208 | + ); |
| 209 | + $sorted[$row->rev_timestamp] = $r; |
| 210 | + } |
| 211 | + krsort($sorted); |
| 212 | + |
| 213 | + $table = "<table class='sortable recordadmin $type-record'>\n<tr> |
| 214 | + <th class='col1'>$type<br></th><th class='col2'>Created<br></th>"; |
| 215 | + foreach (array_keys($this->types) as $k) $table .= "<th class='col$k'>$k<br></th>"; |
| 216 | + $table .= "</tr>\n"; |
| 217 | + $stripe = ''; |
| 218 | + foreach ($sorted as $ts => $r) { |
| 219 | + $ts = preg_replace('|^..(..)(..)(..)(..)(..)..$|', '$3/$2/$1 $4:$5', $ts); |
| 220 | + $t = $r[0]; |
| 221 | + $k = $r[1]; |
| 222 | + $msgView = wfMsg( 'recordadmin-viewlink' ); |
| 223 | + $msgEdit = wfMsg( 'recordadmin-editlink' ); |
| 224 | + $stripe = $stripe ? '' : ' class="stripe"'; |
| 225 | + $table .= "<tr$stripe><td class='col1'>(<a href='".$t->getLocalURL()."'>" .$msgView . "</a>)"; |
| 226 | + $table .= "(<a href='".$title->getLocalURL("wpType=$type&wpRecord=$k")."'>" .$msgEdit . "</a>)</td>\n"; |
| 227 | + $table .= "<td class='col2'>$ts</td>\n"; |
| 228 | + $i = 0; |
| 229 | + foreach (array_keys($this->types) as $k) { |
| 230 | + $v = isset($r[$k]) ? $r[$k] : ' '; |
| 231 | + $table .= "<td class='col$k'>$v</td>"; |
| 232 | + } |
| 233 | + $table .= "</tr>\n"; |
| 234 | + } |
| 235 | + $table .= "</table>\n"; |
| 236 | + $wgOut->addHTML($table); |
| 237 | + } else $wgOut->addWikiText(wfMsg('recordadmin-nomatch')."\n"); |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + # A specific record has been selected, render form for updating |
| 242 | + else { |
| 243 | + $wgOut->addWikiText("== ".wfMsg('recordadmin-edit',$record)." ==\n"); |
| 244 | + $article = new Article(Title::newFromText($record)); |
| 245 | + $text = $article->fetchContent(); |
| 246 | + |
| 247 | + # Update article if form posted |
| 248 | + if (count($posted)) { |
| 249 | + |
| 250 | + # Get the location and length of the record braces to replace |
| 251 | + foreach ($this->examineBraces($text) as $brace) if ($brace['NAME'] == $type) $braces = $brace; |
| 252 | + |
| 253 | + # Attempt to save the article |
| 254 | + $summary = "[[Special:RecordAdmin/$type|RecordAdmin]]: ".wfMsg('recordadmin-typeupdated',$type); |
| 255 | + $replace = ''; |
| 256 | + foreach ($posted as $k => $v) if ($v) { |
| 257 | + if ($this->types[$k] == 'bool') $v = 'yes'; |
| 258 | + $replace .= "| $k = $v\n"; |
| 259 | + } |
| 260 | + $replace = $replace ? "{{"."$type\n$replace}}" : "{{"."$type}}"; |
| 261 | + $text = substr_replace($text, $replace, $braces['OFFSET'], $braces['LENGTH']); |
| 262 | + $success = $article->doEdit($text, $summary, EDIT_UPDATE); |
| 263 | + |
| 264 | + # Report success or error |
| 265 | + if ($success) $wgOut->addHTML("<div class='successbox'>".wfMsg('recordadmin-updatesuccess',$type)."</div>\n"); |
| 266 | + else $wgOut->addHTML("<div class='errorbox'>".wfMsg('recordadmin-updateerror')."</div>\n"); |
| 267 | + $wgOut->addHTML("<br><br><br><br>\n"); |
| 268 | + } |
| 269 | + |
| 270 | + # Populate the form with the current values in the article |
| 271 | + foreach ($this->examineBraces($text) as $brace) if ($brace['NAME'] == $type) $braces = $brace; |
| 272 | + $this->populateForm(substr($text, $braces['OFFSET'], $braces['LENGTH'])); |
| 273 | + |
| 274 | + # Render the form |
| 275 | + $wgOut->addHTML(wfElement('form', array('class' => 'recordadmin', 'action' => $title->getLocalURL('action=submit'), 'method' => 'post'), null)); |
| 276 | + $wgOut->addHTML($this->form); |
| 277 | + $wgOut->addHTML(wfElement('input', array('type' => 'hidden', 'name' => 'wpType', 'value' => $type))); |
| 278 | + $wgOut->addHTML(wfElement('input', array('type' => 'hidden', 'name' => 'wpRecord', 'value' => $record))); |
| 279 | + $wgOut->addHTML('<br><hr><br><table width="100%"><tr>' |
| 280 | + .'<td>'.wfElement('input', array('type' => 'submit', 'value' => wfMsg('recordadmin-buttonsave'))).'</td>' |
| 281 | + .'<td width="100%" align="left">'.wfElement('input', array('type' => 'reset', 'value' => wfMsg('recordadmin-buttonreset'))).'</td>' |
| 282 | + .'</tr></table></form>' |
| 283 | + ); |
| 284 | + } |
| 285 | + } |
| 286 | + |
| 287 | + /** |
| 288 | + * Read in and prepare the form (for use as a search filter) for passed record type |
| 289 | + * - we're using the record's own form as a filter for searching for records |
| 290 | + * - extract only the content from between the form tags and remove any submit inputs |
| 291 | + */ |
| 292 | + function preProcessForm($type) { |
| 293 | + $title = Title::newFromText($type, NS_FORM); |
| 294 | + if ($title->exists()) { |
| 295 | + $form = new Article($title); |
| 296 | + $form = $form->getContent(); |
| 297 | + $form = preg_replace('#<input.+?type=[\'"]?submit["\']?.+?/(input| *)>#', '', $form); # remove submits |
| 298 | + $form = preg_replace('#^.+?<form.+?>#s', '', $form); # remove up to and including form open |
| 299 | + $form = preg_replace('#</form>.+?$#s', '', $form); # remove form close and everything after |
| 300 | + $form = preg_replace('#name\s*=\s*([\'"])(.*?)\\1#s', 'name="ra_$2"', $form); # prefix input names with ra_ |
| 301 | + $form = preg_replace('#(<select.+?>)\s*(?!<option/>)#s', '$1<option selected/>', $form); # ensure all select lists have default blank |
| 302 | + } |
| 303 | + |
| 304 | + # Create a red link to the form if it doesn't exist |
| 305 | + else { |
| 306 | + $form = "<b>".wfMsg('recordadmin-noform',$type)."</b>" |
| 307 | + ."<br><br>".wfMsg('recordadmin-createlink',$title->getLocalURL('action=edit') )."</div>"; |
| 308 | + } |
| 309 | + $this->form = $form; |
| 310 | + } |
| 311 | + |
| 312 | + |
| 313 | + /** |
| 314 | + * Populates the form values from the passed values |
| 315 | + * - $form is HTML text |
| 316 | + * - $values may be a hash or wikitext template syntax |
| 317 | + */ |
| 318 | + function populateForm($values) { |
| 319 | + |
| 320 | + # If values are wikitext, convert to hash |
| 321 | + if (!is_array($values)) { |
| 322 | + $text = $values; |
| 323 | + $values = array(); |
| 324 | + preg_match_all("|\|\s*(.+?)\s*=\s*(.*?)\s*(?=[\|\}])|s", $text, $m); |
| 325 | + foreach ($m[1] as $i => $k) $values[$k] = $m[2][$i]; |
| 326 | + } |
| 327 | + |
| 328 | + # Add the values into the form's HTML depending on their type |
| 329 | + foreach($this->types as $k => $type) { |
| 330 | + |
| 331 | + # Get this input element's html text and position and length |
| 332 | + preg_match("|<([a-zA-Z]+)[^<]+?name=\"ra_$k\".*?>(.*?</\\1>)?|s", $this->form, $m, PREG_OFFSET_CAPTURE); |
| 333 | + list($html, $pos) = $m[0]; |
| 334 | + $len = strlen($html); |
| 335 | + |
| 336 | + # Modify the element according to its type |
| 337 | + # - clears default value, then adds new value |
| 338 | + $v = isset($values[$k]) ? $values[$k] : ''; |
| 339 | + switch ($type) { |
| 340 | + case 'text': |
| 341 | + $html = preg_replace("|value\s*=\s*\".*?\"|", "", $html); |
| 342 | + if ($v) $html = preg_replace("|(/?>)$|", " value=\"$v\" $1", $html); |
| 343 | + break; |
| 344 | + case 'bool': |
| 345 | + $html = preg_replace("|checked|", "", $html); |
| 346 | + if ($v) $html = preg_replace("|(/?>)$|", " checked $1", $html); |
| 347 | + break; |
| 348 | + case 'list': |
| 349 | + $html = preg_replace("|(<option[^<>]*) selected|", "$1", $html); |
| 350 | + if ($v) $html = preg_replace("|(?<=<option)(?=>$v</option>)|s", " selected", $html); |
| 351 | + break; |
| 352 | + case 'blob': |
| 353 | + $html = preg_replace("|>.*?(?=</textarea>)|s", ">$v", $html); |
| 354 | + break; |
| 355 | + } |
| 356 | + |
| 357 | + # Replace the element in the form with the modified html |
| 358 | + $this->form = substr_replace($this->form, $html, $pos, $len); |
| 359 | + } |
| 360 | + } |
| 361 | + |
| 362 | + /** |
| 363 | + * Returns an array of types used by the passed HTML text form |
| 364 | + * - supported types, text, select, checkbox, textarea |
| 365 | + */ |
| 366 | + function examineForm() { |
| 367 | + $this->types = array(); |
| 368 | + preg_match_all("|<([a-zA-Z]+)[^<]+?name=\"ra_(.+?)\".*?>|", $this->form, $m); |
| 369 | + foreach ($m[2] as $i => $k) { |
| 370 | + $tag = $m[1][$i]; |
| 371 | + $type = preg_match("|type\s*=\s*\"(.+?)\"|", $m[0][$i], $n) ? $n[1] : ''; |
| 372 | + switch ($tag) { |
| 373 | + case 'input': |
| 374 | + switch ($type) { |
| 375 | + case 'checkbox': |
| 376 | + $this->types[$k] = 'bool'; |
| 377 | + break; |
| 378 | + default: |
| 379 | + $this->types[$k] = 'text'; |
| 380 | + break; |
| 381 | + } |
| 382 | + break; |
| 383 | + case 'select': |
| 384 | + $this->types[$k] = 'list'; |
| 385 | + break; |
| 386 | + case 'textarea': |
| 387 | + $this->types[$k] = 'blob'; |
| 388 | + break; |
| 389 | + } |
| 390 | + } |
| 391 | + } |
| 392 | + |
| 393 | + /** |
| 394 | + * Return array of braces used and the name, position, length and depth |
| 395 | + * See http://www.organicdesign.co.nz/MediaWiki_code_snippets |
| 396 | + */ |
| 397 | + function examineBraces(&$content) { |
| 398 | + $braces = array(); |
| 399 | + $depths = array(); |
| 400 | + $depth = 1; |
| 401 | + $index = 0; |
| 402 | + while (preg_match('/\\{\\{\\s*([#a-z0-9_]*)|\\}\\}/is', $content, $match, PREG_OFFSET_CAPTURE, $index)) { |
| 403 | + $index = $match[0][1]+2; |
| 404 | + if ($match[0][0] == '}}') { |
| 405 | + $brace =& $braces[$depths[$depth-1]]; |
| 406 | + $brace['LENGTH'] = $match[0][1]-$brace['OFFSET']+2; |
| 407 | + $brace['DEPTH'] = $depth--; |
| 408 | + } |
| 409 | + else { |
| 410 | + $depths[$depth++] = count($braces); |
| 411 | + $braces[] = array( |
| 412 | + 'NAME' => $match[1][0], |
| 413 | + 'OFFSET' => $match[0][1] |
| 414 | + ); |
| 415 | + } |
| 416 | + } |
| 417 | + return $braces; |
| 418 | + } |
| 419 | + |
| 420 | + /** |
| 421 | + * A callback for processing public forms |
| 422 | + */ |
| 423 | + function createRecord() { |
| 424 | + global $wgRequest, $wgRecordAdminUseNamespaces; |
| 425 | + $type = $wgRequest->getText('wpType'); |
| 426 | + $title = $wgRequest->getText('wpTitle'); |
| 427 | + |
| 428 | + # Get types in this kind of record from form |
| 429 | + $this->preProcessForm($type); |
| 430 | + $this->examineForm(); |
| 431 | + |
| 432 | + # Use guid if no title specified |
| 433 | + if (empty($title)) { |
| 434 | + $title = $this->guid; |
| 435 | + if ($wgRecordAdminUseNamespaces) $title = "$type:$title"; |
| 436 | + } |
| 437 | + |
| 438 | + # Attempt to create the article |
| 439 | + $title = Title::newFromText($title); |
| 440 | + if (is_object($title) && !$title->exists()) { |
| 441 | + $article = new Article($title); |
| 442 | + $summary = wfMsg('recordadmin-newcreated'); |
| 443 | + $text = ''; |
| 444 | + foreach ($_POST as $k => $v) if ($v && isset($this->types[$k])) { |
| 445 | + if ($this->types[$k] == 'bool') $v = 'yes'; |
| 446 | + $text .= "| $k = $v\n"; |
| 447 | + } |
| 448 | + $text = $text ? "{{"."$type\n$text}}" : "{{"."$type}}"; |
| 449 | + $success = $article->doEdit($text, $summary, EDIT_NEW); |
| 450 | + } |
| 451 | + } |
| 452 | + |
| 453 | + # If a record was created by a public form, make last 5 digits of ID available via a tag |
| 454 | + function expandTag($text, $argv, &$parser) { |
| 455 | + $parser->mOutput->mCacheTime = -1; |
| 456 | + return $this->guid ? substr($this->guid, -5) : ''; |
| 457 | + } |
| 458 | + |
| 459 | +} |
| 460 | + |
| 461 | +/** |
| 462 | + * Called from $wgExtensionFunctions array when initialising extensions |
| 463 | + */ |
| 464 | +function wfSetupRecordAdmin() { |
| 465 | + global $wgSpecialRecordAdmin, $wgParser, $wgLanguageCode, $wgMessageCache, $wgRequest; |
| 466 | + |
| 467 | + # Make a global singleton so methods are accessible as callbacks etc |
| 468 | + $wgSpecialRecordAdmin = new SpecialRecordAdmin(); |
| 469 | + |
| 470 | + # Make recordID's of articles created with public forms available via recordid tag |
| 471 | + $wgParser->setHook('recordid', array($wgSpecialRecordAdmin, 'expandTag')); |
| 472 | + |
| 473 | + # Check if posting a public creation form |
| 474 | + $title = Title::newFromText($wgRequest->getText('title')); |
| 475 | + if (is_object($title) && $title->getNamespace() != NS_SPECIAL && $wgRequest->getText('wpType') && $wgRequest->getText('wpCreate')) |
| 476 | + $wgSpecialRecordAdmin->createRecord(); |
| 477 | + |
| 478 | + # Add the specialpage to the environment |
| 479 | + SpecialPage::addPage($wgSpecialRecordAdmin); |
| 480 | +} |
Property changes on: trunk/extensions/RecordAdmin/RecordAdmin.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
Added: svn:eol-style |
1 | 481 | + native |
Index: trunk/extensions/Translate/groups/mediawiki-defines.txt |
— | — | @@ -562,8 +562,7 @@ |
563 | 563 | Random Users With Avatars |
564 | 564 | descmsg = random-users-avatars-desc |
565 | 565 | |
566 | | -Record Administration |
567 | | -file = RecordAdmin/SpecialRecordAdmin.18n.php |
| 566 | +Record Admin |
568 | 567 | |
569 | 568 | Refresh Special |
570 | 569 | |