├── ScriptoPlugin.php ├── build.xml ├── controllers └── IndexController.php ├── languages └── template.base.pot ├── libraries ├── Scripto.php └── Scripto │ ├── Adapter │ ├── Example.php │ ├── Exception.php │ └── Interface.php │ ├── Document.php │ ├── Exception.php │ └── Service │ ├── Exception.php │ └── MediaWiki.php ├── models └── ScriptoAdapterOmeka.php ├── plugin.ini ├── routes.ini └── views ├── admin └── plugins │ └── scripto-config-form.php └── shared ├── index ├── diff.php ├── history.php ├── index.php ├── login.php ├── recent-changes.php ├── revision.php ├── transcribe.php └── watchlist.php └── javascripts ├── OpenLayers.js ├── img ├── east-mini.png ├── north-mini.png ├── south-mini.png ├── west-mini.png ├── zoom-minus-mini.png ├── zoom-plus-mini.png └── zoom-world-mini.png └── theme └── default └── style.css /ScriptoPlugin.php: -------------------------------------------------------------------------------- 1 | array( 43 | // gif 44 | 'image/gif', 'image/x-xbitmap', 'image/gi_', 45 | // jpg 46 | 'image/jpeg', 'image/jpg', 'image/jpe_', 'image/pjpeg', 47 | 'image/vnd.swiftview-jpeg', 48 | // png 49 | 'image/png', 'application/png', 'application/x-png', 50 | // bmp 51 | 'image/bmp', 'image/x-bmp', 'image/x-bitmap', 52 | 'image/x-xbitmap', 'image/x-win-bitmap', 53 | 'image/x-windows-bmp', 'image/ms-bmp', 'image/x-ms-bmp', 54 | 'application/bmp', 'application/x-bmp', 55 | 'application/x-win-bitmap', 56 | ), 57 | 'fileExtensions' => array( 58 | 'gif', 'jpeg', 'jpg', 'jpe', 'png', 'bmp', 59 | ), 60 | ); 61 | 62 | /** 63 | * @var MIME types compatible with Google Docs viewer. 64 | */ 65 | public static $fileIdentifiersGoogleDocs = array( 66 | 'mimeTypes' => array( 67 | // pdf 68 | 'application/pdf', 'application/x-pdf', 69 | 'application/acrobat', 'applications/vnd.pdf', 'text/pdf', 70 | 'text/x-pdf', 71 | // docx 72 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 73 | // doc 74 | 'application/msword', 'application/doc', 'appl/text', 75 | 'application/vnd.msword', 'application/vnd.ms-word', 76 | 'application/winword', 'application/word', 'application/vnd.ms-office', 77 | 'application/x-msw6', 'application/x-msword', 78 | // ppt 79 | 'application/vnd.ms-powerpoint', 'application/mspowerpoint', 80 | 'application/ms-powerpoint', 'application/mspowerpnt', 81 | 'application/vnd-mspowerpoint', 'application/powerpoint', 82 | 'application/x-powerpoint', 'application/x-m', 83 | // pptx 84 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 85 | // xls 86 | 'application/vnd.ms-excel', 'application/msexcel', 87 | 'application/x-msexcel', 'application/x-ms-excel', 88 | 'application/vnd.ms-excel', 'application/x-excel', 89 | 'application/x-dos_ms_excel', 'application/xls', 90 | // xlsx 91 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 92 | // tiff 93 | 'image/tiff', 94 | // ps, ai 95 | 'application/postscript', 'application/ps', 96 | 'application/x-postscript', 'application/x-ps', 97 | 'text/postscript', 'application/x-postscript-not-eps', 98 | // eps 99 | 'application/eps', 'application/x-eps', 'image/eps', 100 | 'image/x-eps', 101 | // psd 102 | 'image/vnd.adobe.photoshop', 'image/photoshop', 103 | 'image/x-photoshop', 'image/psd', 'application/photoshop', 104 | 'application/psd', 'zz-application/zz-winassoc-psd', 105 | // dxf 106 | 'application/dxf', 'application/x-autocad', 107 | 'application/x-dxf', 'drawing/x-dxf', 'image/vnd.dxf', 108 | 'image/x-autocad', 'image/x-dxf', 109 | 'zz-application/zz-winassoc-dxf', 110 | // xvg 111 | 'image/svg+xml', 112 | // xps 113 | 'application/vnd.ms-xpsdocument', 114 | ), 115 | 'fileExtensions' => array( 116 | 'pdf', 117 | 'docx', 118 | 'doc', 'dot', 119 | 'ppt', 'pps', 'pot', 120 | 'pptx', 121 | 'xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw', 122 | 'xlsx', 123 | 'tiff', 'tif', 124 | 'ai', 'eps', 'ps', 125 | 'psd', 126 | 'dxf', 127 | 'xvg', 128 | 'xps', 129 | ), 130 | ); 131 | 132 | /** 133 | * Initialize Scripto. 134 | */ 135 | public function hookInitialize() 136 | { 137 | // Add translation. 138 | add_translation_source(dirname(__FILE__) . '/languages'); 139 | } 140 | 141 | /** 142 | * Install Scripto. 143 | */ 144 | public function hookInstall() 145 | { 146 | // Don't install if an element set by the name "Scripto" already exists. 147 | if ($this->_db->getTable('ElementSet')->findByName(self::ELEMENT_SET_NAME)) { 148 | throw new Omeka_Plugin_Installer_Exception( 149 | __('An element set by the name "%s" already exists. You must delete ' 150 | . 'that element set to install this plugin.', self::ELEMENT_SET_NAME) 151 | ); 152 | } 153 | 154 | $elementSetMetadata = array('name' => self::ELEMENT_SET_NAME); 155 | $elements = array( 156 | array('name' => 'Transcription', 157 | 'description' => 'A written representation of a document.') 158 | ); 159 | insert_element_set($elementSetMetadata, $elements); 160 | } 161 | 162 | /** 163 | * Uninstall Scripto. 164 | */ 165 | public function hookUninstall() 166 | { 167 | // Delete the Scripto element set. 168 | $this->_db->getTable('ElementSet')->findByName(self::ELEMENT_SET_NAME)->delete(); 169 | 170 | // Delete options that are specific to Scripto. 171 | delete_option('scripto_mediawiki_api_url'); 172 | delete_option('scripto_image_viewer'); 173 | delete_option('scripto_use_google_docs_viewer'); 174 | delete_option('scripto_import_type'); 175 | delete_option('scripto_home_page_text'); 176 | } 177 | 178 | /** 179 | * Appends a warning message to the uninstall confirmation page. 180 | */ 181 | public function hookUninstallMessage() 182 | { 183 | echo '

' . __( 184 | '%1$sWarning%2$s: This will permanently delete the "%3$s" element set and ' 185 | . 'all transcriptions imported from MediaWiki. You may deactivate this ' 186 | . 'plugin if you do not want to lose data. Uninstalling this plugin will ' 187 | . 'not affect your MediaWiki database in any way.', 188 | '', '', self::ELEMENT_SET_NAME) . '

'; 189 | } 190 | 191 | /** 192 | * Define routes. 193 | * 194 | * @param Zend_Controller_Router_Rewrite $router 195 | */ 196 | public function hookDefineRoutes($args) 197 | { 198 | $args['router']->addConfig(new Zend_Config_Ini(dirname(__FILE__) . '/routes.ini', 'routes')); 199 | } 200 | 201 | /** 202 | * Render the config form. 203 | */ 204 | public function hookConfigForm() 205 | { 206 | // Set form defaults. 207 | $imageViewer = get_option('scripto_image_viewer'); 208 | if (!in_array($imageViewer, array('openlayers'))) { 209 | $imageViewer = 'default'; 210 | } 211 | $useGoogleDocsViewer = get_option('scripto_use_google_docs_viewer'); 212 | if (is_null($useGoogleDocsViewer)) { 213 | $useGoogleDocsViewer = 0; 214 | } 215 | $importType = get_option('scripto_import_type'); 216 | if (is_null($importType)) { 217 | $importType = 'html'; 218 | } 219 | 220 | echo get_view()->partial( 221 | 'plugins/scripto-config-form.php', 222 | array('image_viewer' => $imageViewer, 223 | 'use_google_docs_viewer' => $useGoogleDocsViewer, 224 | 'import_type' => $importType) 225 | ); 226 | } 227 | 228 | /** 229 | * Handle a submitted config form. 230 | */ 231 | public function hookConfig() 232 | { 233 | // Validate the MediaWiki API URL. 234 | if (!Scripto::isValidApiUrl($_POST['scripto_mediawiki_api_url'])) { 235 | throw new Omeka_Plugin_Installer_Exception('Invalid MediaWiki API URL'); 236 | } 237 | 238 | // Set options that are specific to Scripto. 239 | set_option('scripto_mediawiki_api_url', $_POST['scripto_mediawiki_api_url']); 240 | set_option('scripto_mediawiki_cookie_prefix', $_POST['scripto_mediawiki_cookie_prefix']); 241 | set_option('scripto_image_viewer', $_POST['scripto_image_viewer']); 242 | set_option('scripto_use_google_docs_viewer', $_POST['scripto_use_google_docs_viewer']); 243 | set_option('scripto_import_type', $_POST['scripto_import_type']); 244 | set_option('scripto_home_page_text', $_POST['scripto_home_page_text']); 245 | } 246 | 247 | 248 | /** 249 | * Append the transcribe link to the public items show page. 250 | */ 251 | public function hookPublicItemsShow() 252 | { 253 | $this->_appendToItemsShow(); 254 | } 255 | 256 | /** 257 | * Append the transcribe link to the admin items show page. 258 | */ 259 | public function hookAdminItemsShow() 260 | { 261 | $this->_appendToItemsShow(); 262 | } 263 | 264 | /** 265 | * Add Scripto to the admin navigation. 266 | * 267 | * @param array $nav 268 | * @return array 269 | */ 270 | public function filterAdminNavigationMain($nav) 271 | { 272 | $nav[] = array('label' => __('Scripto'), 'uri' => url('scripto')); 273 | return $nav; 274 | } 275 | 276 | /** 277 | * Add Scripto to the public navigation. 278 | * 279 | * @param array $nav 280 | * @return array 281 | */ 282 | public function filterPublicNavigationMain($nav) 283 | { 284 | $nav[] = array('label' => __('Scripto'), 'uri' => url('scripto')); 285 | return $nav; 286 | } 287 | 288 | /** 289 | * Append the transcribe link to the items show page. 290 | */ 291 | protected function _appendToItemsShow() 292 | { 293 | $item = get_current_record('item'); 294 | $scripto = self::getScripto(); 295 | // Do not show page links if document is not valid. 296 | if (!$scripto->documentExists($item->id)) { 297 | return; 298 | } 299 | $doc = $scripto->getDocument($item->id); 300 | ?> 301 |

302 |
    303 | getPages() as $pageId => $pageName): ?> 304 |
  1. 308 | 309 |
310 | getWebPath('original'); 322 | $imageSize = ScriptoPlugin::getImageSize($imageUrl, 250); 323 | 324 | ?> 325 | 338 |
339 | setQuery(array('url' => $file->getWebPath('original'), 352 | 'embedded' => 'true')); 353 | echo ''; 354 | } 355 | 356 | /** 357 | * Convenience method to get the Scripto object. 358 | * 359 | * @param string $apiUrl 360 | */ 361 | public static function getScripto($apiUrl = null) 362 | { 363 | if (null === $apiUrl) { 364 | $apiUrl = get_option('scripto_mediawiki_api_url'); 365 | } 366 | $cookiePrefix = get_option('scripto_mediawiki_cookie_prefix'); 367 | 368 | return new Scripto(new ScriptoAdapterOmeka, array( 369 | 'api_url' => $apiUrl, 370 | 'cookie_prefix' => $cookiePrefix ? $cookiePrefix : null, 371 | )); 372 | } 373 | 374 | /** 375 | * Return a truncated string with left and right padding. 376 | * 377 | * Primarily used for truncating long document page names that would 378 | * otherwise break tables. 379 | * 380 | * @param string $str The string to truncate. 381 | * @param int $length The trancate length. 382 | * @param string $default The string to return if the string is empty. 383 | * @return string 384 | */ 385 | public static function truncate($str, $length, $default = '') 386 | { 387 | $str = trim($str); 388 | if (empty($str)) { 389 | return $default; 390 | } 391 | if (strlen($str) <= $length) { 392 | return $str; 393 | } 394 | $padding = floor($length / 2); 395 | return preg_replace('/^(.{' . $padding . '}).*(.{' . $padding . '})$/', '$1... $2', $str); 396 | } 397 | 398 | /** 399 | * Get dimensions of the provided image. 400 | * 401 | * @param string $filename URI to file. 402 | * @param int $width Width constraint. 403 | * @return array 404 | */ 405 | public static function getImageSize($filename, $width = null) 406 | { 407 | $size = getimagesize($filename); 408 | if (!$size) { 409 | return false; 410 | } 411 | if (is_int($width)) { 412 | $height = round(($width * $size[1]) / $size[0]); 413 | } else { 414 | $width = $size[1]; 415 | $height = $size[0]; 416 | } 417 | return array('width' => $width, 'height' => $height); 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /controllers/IndexController.php: -------------------------------------------------------------------------------- 1 | getRequest()->getActionName()) { 15 | 16 | // Image viewers. 17 | switch (get_option('scripto_image_viewer')) { 18 | case 'openlayers': 19 | add_file_display_callback(ScriptoPlugin::$fileIdentifiersOpenLayers, 'ScriptoPlugin::openLayers'); 20 | break; 21 | default: 22 | // Do nothing. Use Omeka default file display stategy. 23 | break; 24 | } 25 | 26 | // Google Docs viewer. 27 | if (get_option('scripto_use_google_docs_viewer')) { 28 | add_file_display_callback(ScriptoPlugin::$fileIdentifiersGoogleDocs, 'ScriptoPlugin::googleDocs'); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * View document pages to which you have contributed. 35 | */ 36 | public function indexAction() 37 | { 38 | try { 39 | $scripto = ScriptoPlugin::getScripto(); 40 | $documentPages = array(); 41 | // Don't bother getting the user document pages if not logged in. 42 | if ($scripto->isLoggedIn()) { 43 | $documentPages = $scripto->getUserDocumentPages(500); 44 | } 45 | } catch (Scripto_Exception $e) { 46 | $this->_helper->flashMessenger($e->getMessage()); 47 | } 48 | 49 | $this->view->scripto = $scripto; 50 | $this->view->documentPages = $documentPages; 51 | $this->view->homePageText = trim(get_option('scripto_home_page_text')); 52 | } 53 | 54 | /** 55 | * Log in to Scripto. 56 | */ 57 | public function loginAction() 58 | { 59 | try { 60 | $scripto = ScriptoPlugin::getScripto(); 61 | // Handle a login. 62 | if ($this->_getParam('scripto_mediawiki_login')) { 63 | $scripto->login($this->_getParam('scripto_mediawiki_username'), 64 | $this->_getParam('scripto_mediawiki_password')); 65 | $this->_helper->flashMessenger(__('Successfully logged into Scripto.'), 'success'); 66 | } 67 | // Redirect if logged in. 68 | if ($scripto->isLoggedIn()) { 69 | if ($this->_getParam('scripto_redirect_url')) { 70 | $this->_helper->redirector->gotoUrl($this->_getParam('scripto_redirect_url')); 71 | } else { 72 | $this->_helper->redirector->goto('index'); 73 | } 74 | } 75 | } catch (Scripto_Service_Exception $e) { 76 | $this->_helper->flashMessenger($e->getMessage()); 77 | } 78 | 79 | // Set the URL to redirect to on a sucessful login. 80 | $redirectUrl = null; 81 | if ($this->_getParam('scripto_redirect_url')) { 82 | // Assume login error and reassign the parameter. 83 | $redirectUrl = $this->_getParam('scripto_redirect_url'); 84 | } else if ('scripto' == $this->getRequest()->getModuleName() && $_SERVER['HTTP_REFERER']) { 85 | // Assign HTTP referer to scripto_redirect_url parameter only if 86 | // coming from the Scripto application. 87 | $redirectUrl = $_SERVER['HTTP_REFERER']; 88 | } 89 | 90 | $this->view->redirectUrl = $redirectUrl; 91 | $this->view->scripto = $scripto; 92 | } 93 | 94 | /** 95 | * Log out of Scripto. 96 | */ 97 | public function logoutAction() 98 | { 99 | try { 100 | $scripto = ScriptoPlugin::getScripto(); 101 | $scripto->logout(); 102 | $this->_helper->flashMessenger(__('Successfully logged out of Scripto.'), 'success'); 103 | } catch (Scripto_Exception $e) { 104 | $this->_helper->flashMessenger($e->getMessage()); 105 | } 106 | 107 | // Always redirect. 108 | $this->_helper->redirector->goto('index'); 109 | } 110 | 111 | /** 112 | * View your watchlist. 113 | */ 114 | public function watchlistAction() 115 | { 116 | try { 117 | $scripto = ScriptoPlugin::getScripto(); 118 | // Anonymous users 119 | if (!$scripto->isLoggedIn()) { 120 | $this->_helper->redirector->goto('index'); 121 | } 122 | $watchlist = $scripto->getWatchlist(500); 123 | } catch (Scripto_Exception $e) { 124 | $this->_helper->flashMessenger($e->getMessage()); 125 | } 126 | 127 | $this->view->scripto = $scripto; 128 | $this->view->watchlist = $watchlist; 129 | } 130 | 131 | /** 132 | * View recent changes to the document pages. 133 | */ 134 | public function recentChangesAction() 135 | { 136 | try { 137 | $scripto = ScriptoPlugin::getScripto(); 138 | $recentChanges = $scripto->getRecentChanges(500); 139 | } catch (Scripto_Exception $e) { 140 | $this->_helper->flashMessenger($e->getMessage()); 141 | } 142 | 143 | $this->view->scripto = $scripto; 144 | $this->view->recentChanges = $recentChanges; 145 | } 146 | 147 | /** 148 | * View transcription interface. 149 | */ 150 | public function transcribeAction() 151 | { 152 | try { 153 | $scripto = ScriptoPlugin::getScripto(); 154 | $doc = $scripto->getDocument($this->_getParam('item-id')); 155 | $doc->setPage($this->_getParam('file-id')); 156 | 157 | // Set the File object. 158 | $file = $this->_helper->db->getTable('File')->find($doc->getPageId()); 159 | 160 | // Set the page HTML. 161 | $transcriptionPageHtml = Scripto::removeHtmlAttributes($doc->getTranscriptionPageHtml()); 162 | $talkPageHtml = Scripto::removeHtmlAttributes($doc->getTalkPageHtml()); 163 | 164 | // Set all the document's pages. 165 | $pages = $doc->getPages(); 166 | 167 | // Set the pagination. 168 | $paginationUrls = array(); 169 | foreach ($pages as $pageId => $pageName) { 170 | if (isset($current)) { 171 | $paginationUrls['next'] = $this->view->url(array( 172 | 'action' => 'transcribe', 173 | 'item-id' => $doc->getId(), 174 | 'file-id' => $pageId 175 | ), 'scripto_action_item_file'); 176 | break; 177 | } 178 | if ($pageId == $doc->getPageId()) { 179 | $current = true; 180 | } else { 181 | $paginationUrls['previous'] = $this->view->url(array( 182 | 'action' => 'transcribe', 183 | 'item-id' => $doc->getId(), 184 | 'file-id' => $pageId 185 | ), 'scripto_action_item_file'); 186 | } 187 | } 188 | 189 | } catch (Scripto_Exception $e) { 190 | $this->_helper->flashMessenger($e->getMessage()); 191 | $this->_helper->redirector->goto('index'); 192 | } 193 | 194 | $this->view->file = $file; 195 | $this->view->transcriptionPageHtml = $transcriptionPageHtml; 196 | $this->view->talkPageHtml = $talkPageHtml; 197 | $this->view->paginationUrls = $paginationUrls; 198 | $this->view->scripto = $scripto; 199 | $this->view->doc = $doc; 200 | } 201 | 202 | /** 203 | * View page history. 204 | */ 205 | public function historyAction() 206 | { 207 | try { 208 | $scripto = ScriptoPlugin::getScripto(); 209 | $doc = $scripto->getDocument($this->_getParam('item-id')); 210 | $doc->setPage($this->_getParam('file-id')); 211 | 212 | // Set the history depending on namespace index. 213 | if (1 == $this->_getParam('namespace-index')) { 214 | $info = $doc->getTalkPageInfo(); 215 | $history = $doc->getTalkPageHistory(100); 216 | } else { 217 | $info = $doc->getTranscriptionPageInfo(); 218 | $history = $doc->getTranscriptionPageHistory(100); 219 | } 220 | } catch (Scripto_Exception $e) { 221 | $this->_helper->flashMessenger($e->getMessage()); 222 | $this->_helper->redirector->goto('index'); 223 | } 224 | 225 | $this->view->scripto = $scripto; 226 | $this->view->doc = $doc; 227 | $this->view->info = $info; 228 | $this->view->history = $history; 229 | $this->view->namespaceIndex = $this->_getParam('namespace-index'); 230 | } 231 | 232 | /** 233 | * View a page revision. 234 | */ 235 | public function revisionAction() 236 | { 237 | try { 238 | $scripto = ScriptoPlugin::getScripto(); 239 | $doc = $scripto->getDocument($this->_getParam('item-id')); 240 | $doc->setPage($this->_getParam('file-id')); 241 | $revision = $scripto->getRevision($this->_getParam('revision-id')); 242 | 243 | // Handle a revert. 244 | if ($this->_getParam('scripto-page-revert')) { 245 | if (1 == $this->_getParam('namespace-index')) { 246 | $doc->editTalkPage($revision['wikitext']); 247 | } else { 248 | $doc->editTranscriptionPage($revision['wikitext']); 249 | } 250 | $this->_helper->flashMessenger(__('Successfully reverted the page to a previous revision.'), 'success'); 251 | $this->_helper->redirector->gotoRoute(array('item-id' => $doc->getId(), 252 | 'file-id' => $doc->getPageId(), 253 | 'namespace-index' => $this->_getParam('namespace-index')), 254 | 'scripto_history'); 255 | } 256 | 257 | } catch (Scripto_Exception $e) { 258 | $this->_helper->flashMessenger($e->getMessage()); 259 | } 260 | 261 | $this->view->scripto = $scripto; 262 | $this->view->doc = $doc; 263 | $this->view->revision = $revision; 264 | $this->view->namespaceIndex = $this->_getParam('namespace-index'); 265 | } 266 | 267 | /** 268 | * View diff between page revisions. 269 | */ 270 | public function diffAction() 271 | { 272 | try { 273 | $scripto = ScriptoPlugin::getScripto(); 274 | $doc = $scripto->getDocument($this->_getParam('item-id')); 275 | $doc->setPage($this->_getParam('file-id')); 276 | $diff = $scripto->getRevisionDiff($this->_getParam('old-revision-id'), $this->_getParam('revision-id')); 277 | $oldRevision = $scripto->getRevision($this->_getParam('old-revision-id')); 278 | $revision = $scripto->getRevision($this->_getParam('revision-id')); 279 | } catch (Scripto_Exception $e) { 280 | $this->_helper->flashMessenger($e->getMessage()); 281 | $this->_helper->redirector->goto('index'); 282 | } 283 | 284 | $this->view->scripto = $scripto; 285 | $this->view->doc = $doc; 286 | $this->view->diff = $diff; 287 | $this->view->namespaceIndex = $this->_getParam('namespace-index'); 288 | $this->view->oldRevision = $oldRevision; 289 | $this->view->revision = $revision; 290 | } 291 | 292 | /** 293 | * Handle AJAX requests from the transcribe action. 294 | * 295 | * 403 Forbidden 296 | * 400 Bad Request 297 | * 500 Internal Server Error 298 | */ 299 | public function pageActionAction() 300 | { 301 | // Don't render the view script. 302 | $this->_helper->viewRenderer->setNoRender(true); 303 | 304 | // Only allow AJAX requests. 305 | if (!$this->getRequest()->isXmlHttpRequest()) { 306 | $this->getResponse()->setHttpResponseCode(403); 307 | return; 308 | } 309 | 310 | // Allow only valid pages. 311 | $pages = array('transcription', 'talk'); 312 | if (!in_array($this->_getParam('page'), $pages)) { 313 | $this->getResponse()->setHttpResponseCode(400); 314 | return; 315 | } 316 | 317 | // Only allow valid page actions. 318 | $pageActions = array('edit', 'watch', 'unwatch', 'protect', 'unprotect', 319 | 'import-page', 'import-document'); 320 | if (!in_array($this->_getParam('page_action'), $pageActions)) { 321 | $this->getResponse()->setHttpResponseCode(400); 322 | return; 323 | } 324 | 325 | // Handle the page action. 326 | try { 327 | $scripto = ScriptoPlugin::getScripto(); 328 | $doc = $scripto->getDocument($this->_getParam('item_id')); 329 | $doc->setPage($this->_getParam('file_id')); 330 | 331 | $body = null; 332 | switch ($this->_getParam('page_action')) { 333 | case 'edit': 334 | if ('talk' == $this->_getParam('page')) { 335 | $doc->editTalkPage($this->_getParam('wikitext')); 336 | $body = $doc->getTalkPageHtml(); 337 | } else { 338 | $doc->editTranscriptionPage($this->_getParam('wikitext')); 339 | $body = $doc->getTranscriptionPageHtml(); 340 | } 341 | break; 342 | case 'watch': 343 | $doc->watchPage(); 344 | break; 345 | case 'unwatch': 346 | $doc->unwatchPage(); 347 | break; 348 | case 'protect': 349 | if ('talk' == $this->_getParam('page')) { 350 | $doc->protectTalkPage(); 351 | } else { 352 | $doc->protectTranscriptionPage(); 353 | } 354 | break; 355 | case 'unprotect': 356 | if ('talk' == $this->_getParam('page')) { 357 | $doc->unprotectTalkPage(); 358 | } else { 359 | $doc->unprotectTranscriptionPage(); 360 | } 361 | break; 362 | case 'import-page': 363 | $doc->exportPage(get_option('scripto_import_type')); 364 | break; 365 | case 'import-document': 366 | $doc->export(get_option('scripto_import_type')); 367 | break; 368 | default: 369 | $this->getResponse()->setHttpResponseCode(400); 370 | return; 371 | } 372 | 373 | $this->getResponse()->setBody($body); 374 | } catch (Scripto_Exception $e) { 375 | $this->getResponse() 376 | ->setHttpResponseCode(500) 377 | ->setBody($e->getMessage()); 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /languages/template.base.pot: -------------------------------------------------------------------------------- 1 | # Translation for the Scripto plugin for Omeka. 2 | # Copyright (C) 2012 Roy Rosenzweig Center for History and New Media 3 | # This file is distributed under the same license as the Omeka package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Scripto\n" 10 | "Report-Msgid-Bugs-To: http://github.com/omeka/plugin-Scripto/issues\n" 11 | "POT-Creation-Date: 2012-11-30 21:49-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | msgid "un/protected" 21 | msgstr "" 22 | 23 | msgid "protected" 24 | msgstr "" 25 | 26 | msgid "unprotected" 27 | msgstr "" 28 | 29 | msgid "replaced" 30 | msgstr "" 31 | 32 | msgid "created" 33 | msgstr "" 34 | 35 | msgid "edited" 36 | msgstr "" 37 | 38 | msgid "protected by" 39 | msgstr "" 40 | 41 | msgid "unprotected by" 42 | msgstr "" 43 | 44 | msgid "replaced by" 45 | msgstr "" 46 | 47 | msgid "created by" 48 | msgstr "" 49 | 50 | msgid "edited by" 51 | msgstr "" 52 | -------------------------------------------------------------------------------- /libraries/Scripto.php: -------------------------------------------------------------------------------- 1 | 58 | *
  • $mediawiki['api_url']: required; the MediaWiki API URL
  • 59 | *
  • $mediawiki['pass_cookies']: optional pass cookies to the web 60 | *
  • $mediawiki['cookie_prefix']: optional; set the cookie prefix 61 | * browser via API client
  • 62 | * 63 | */ 64 | public function __construct(Scripto_Adapter_Interface $adapter, $mediawiki) 65 | { 66 | // Set the adapter. 67 | $this->_adapter = $adapter; 68 | 69 | // Set the MediaWiki service. 70 | if ($mediawiki instanceof Scripto_Service_MediaWiki) { 71 | $this->_mediawiki = $mediawiki; 72 | } else if (is_array($mediawiki) && array_key_exists('api_url', $mediawiki)) { 73 | if (!isset($mediawiki['pass_cookies'])) { 74 | $mediawiki['pass_cookies'] = true; 75 | } 76 | if (!isset($mediawiki['cookie_prefix'])) { 77 | $mediawiki['cookie_prefix'] = null; 78 | } 79 | 80 | $this->_mediawiki = new Scripto_Service_MediaWiki($mediawiki['api_url'], 81 | (bool) $mediawiki['pass_cookies'], 82 | $mediawiki['cookie_prefix']); 83 | } else { 84 | throw new Scripto_Exception('The provided mediawiki parameter is invalid.'); 85 | } 86 | 87 | // Set the user information. 88 | $this->setUserInfo(); 89 | } 90 | 91 | /** 92 | * Provide a transparent interface for calling custom adapter methods. 93 | * 94 | * This makes it possible to call custom adapter methods (those not required 95 | * by Scripto_Adapter_Interface) directly from the Scripto object. 96 | * 97 | * @see Scripto_Adapter_Interface 98 | * @param string $name 99 | * @param array $args 100 | * @return mixed 101 | */ 102 | public function __call($name, $args) 103 | { 104 | if (!method_exists($this->_adapter, $name)) { 105 | require_once 'Scripto/Adapter/Exception.php'; 106 | throw new Scripto_Adapter_Exception('The provided adapter method "' . $name . '" does not exist.'); 107 | } 108 | return call_user_func_array(array($this->_adapter, $name), $args); 109 | } 110 | 111 | /** 112 | * Check whether the specified document exists in the external system. 113 | * 114 | * @uses Scripto_Adapter_Interface::documentExists() 115 | * @param string|int $id The unique document identifier. 116 | * @return bool 117 | */ 118 | public function documentExists($id) 119 | { 120 | // Query the adapter whether the document exists. 121 | if ($this->_adapter->documentExists($id)) { 122 | return true; 123 | } 124 | return false; 125 | } 126 | 127 | /** 128 | * Get a Scripto_Document object. 129 | * 130 | * @see Scripto_Document 131 | * @param string|int $id The unique document identifier. 132 | * @return Scripto_Document 133 | */ 134 | public function getDocument($id) 135 | { 136 | return new Scripto_Document($id, $this->_adapter, $this->_mediawiki); 137 | } 138 | 139 | /** 140 | * Login via the MediaWiki service. 141 | * 142 | * It is possible to restrict account creation in MediaWiki. 143 | * @link http://www.mediawiki.org/wiki/Manual:Preventing_access#Restrict_account_creation 144 | * 145 | * @uses Scripto_Service_MediaWiki::login() 146 | * @param string $username The MediaWiki user's username. 147 | * @param string $password The MediaWiki user's password. 148 | */ 149 | public function login($username, $password) 150 | { 151 | $this->_mediawiki->login($username, $password); 152 | $this->setUserInfo(); 153 | } 154 | 155 | /** 156 | * Logout via the MediaWiki service. 157 | * 158 | * @uses Scripto_Service_MediaWiki::logout() 159 | */ 160 | public function logout() 161 | { 162 | $this->_mediawiki->logout(); 163 | $this->setUserInfo(); 164 | } 165 | 166 | /** 167 | * Determine if the current user is logged in. 168 | * 169 | * @return bool 170 | */ 171 | public function isLoggedIn() 172 | { 173 | // Check against the user ID. An anonymous user has an ID of 0. 174 | return (bool) $this->_userInfo['query']['userinfo']['id']; 175 | } 176 | 177 | /** 178 | * Determine if the current user can export transcriptions to the external 179 | * system. 180 | * 181 | * @param array $groups The MediaWiki groups allowed to export. 182 | * @return bool 183 | */ 184 | public function canExport(array $groups = array('sysop', 'bureaucrat')) 185 | { 186 | foreach ($groups as $group) { 187 | if (in_array($group, $this->_userInfo['query']['userinfo']['groups'])) { 188 | return true; 189 | } 190 | } 191 | return false; 192 | } 193 | 194 | /** 195 | * Determine if the current user can protect MediaWiki pages. 196 | * 197 | * @return bool 198 | */ 199 | public function canProtect() 200 | { 201 | // Users with protect rights can protect pages. 202 | if (in_array('protect', $this->_userInfo['query']['userinfo']['rights'])) { 203 | return true; 204 | } 205 | return false; 206 | } 207 | 208 | /** 209 | * Set the current user's information. 210 | * 211 | * Under normal circumstances calling this method directly is unnecessary, 212 | * but is helpful when authenticating after construction and when a login is 213 | * not called, like when hijacking cookies for command line authentication. 214 | * 215 | * @uses Scripto_Service_MediaWiki::getUserInfo() 216 | */ 217 | public function setUserInfo() 218 | { 219 | $this->_userInfo = $this->_mediawiki->getUserInfo('groups|rights'); 220 | } 221 | 222 | /** 223 | * Return the name of the current user. 224 | * 225 | * @return string 226 | */ 227 | public function getUserName() 228 | { 229 | return $this->_userInfo['query']['userinfo']['name']; 230 | } 231 | 232 | /** 233 | * Get the current user's most recently contributed document pages. 234 | * 235 | * @uses Scripto_Service_MediaWiki::getUserContributions() 236 | * @param int $limit The number of document pages to return. 237 | * @return array 238 | */ 239 | public function getUserDocumentPages($limit = 10) 240 | { 241 | $limit = (int) $limit; 242 | $userDocumentPages = array(); 243 | $documentTitles = array(); 244 | $start = null; 245 | 246 | // Namespaces to get: ns_index => ns_name 247 | // See http://www.mediawiki.org/wiki/Manual:Namespace#Built-in_namespaces 248 | $namespaces = array('0' => 'Main', '1' => 'Talk'); 249 | 250 | do { 251 | $response = $this->_mediawiki->getUserContributions( 252 | $this->_userInfo['query']['userinfo']['name'], 253 | array('ucstart' => $start, 254 | 'ucnamespace' => implode('|', array_keys($namespaces)), 255 | 'uclimit' => 100) 256 | ); 257 | foreach ($response['query']['usercontribs'] as $value) { 258 | 259 | // Filter out duplicate pages. 260 | if (array_key_exists($value['pageid'], $userDocumentPages)) { 261 | continue; 262 | } 263 | 264 | // Extract the title, removing the namespace if any. 265 | $title = preg_replace('/^(.+:)?(.+)$/', '$2', $value['title']); 266 | 267 | // Preempt further processing on contributions with an invalid 268 | // prefix. 269 | if (Scripto_Document::BASE_TITLE_PREFIX != $title[0]) { 270 | continue; 271 | } 272 | 273 | // Set the document ID and page ID. 274 | $documentIds = Scripto_Document::decodeBaseTitle($title); 275 | 276 | // Filter out contributions that are not valid document pages. 277 | if (!$this->_adapter->documentPageExists($documentIds[0], $documentIds[1])) { 278 | continue; 279 | } 280 | 281 | // Set the document title and document page name. Reduce calls 282 | // to the adapter by caching each document title, and checking 283 | // if they exist. 284 | if (array_key_exists($documentIds[0], $documentTitles)) { 285 | $documentTitle = $documentTitles[$documentIds[0]]; 286 | } else { 287 | $documentTitle = $this->_adapter->getDocumentTitle($documentIds[0]); 288 | $documentTitles[$documentIds[0]] = $documentTitle; 289 | } 290 | 291 | // Duplicate pages have already been filtered out, so there is 292 | // no need to cache document page names. 293 | $documentPageName = $this->_adapter->getDocumentPageName($documentIds[0], $documentIds[1]); 294 | 295 | // Build the user document pages, newest properties first. 296 | $userDocumentPages[$value['pageid']] = array( 297 | 'revision_id' => $value['revid'], 298 | 'namespace_index' => $value['ns'], 299 | 'namespace_name' => $namespaces[$value['ns']], 300 | 'mediawiki_title' => $value['title'], 301 | 'timestamp' => $value['timestamp'], 302 | 'comment' => $value['comment'], 303 | 'size' => $value['size'], 304 | 'document_id' => $documentIds[0], 305 | 'document_page_id' => $documentIds[1], 306 | 'document_title' => $documentTitle, 307 | 'document_page_name' => $documentPageName, 308 | ); 309 | 310 | // Break out of the loops if limit has been reached. 311 | if ($limit == count($userDocumentPages)) { 312 | break 2; 313 | } 314 | } 315 | 316 | // Set the query continue, if any. 317 | if (isset($response['query-continue'])) { 318 | $start = $response['query-continue']['usercontribs']['ucstart']; 319 | } else { 320 | $start = null; 321 | } 322 | 323 | } while ($start); 324 | 325 | return $userDocumentPages; 326 | } 327 | 328 | /** 329 | * Get the recent changes. 330 | * 331 | * @link http://www.mediawiki.org/wiki/Manual:Namespace#Built-in_namespaces 332 | * @uses Scripto_Service_MediaWiki::getRecentChanges() 333 | * @param int $limit The number of recent changes to return. 334 | * @return array 335 | */ 336 | public function getRecentChanges($limit = 10) 337 | { 338 | $start = null; 339 | $recentChanges = array(); 340 | $documentTitles = array(); 341 | $documentPageNames = array(); 342 | 343 | // Namespaces to get: ns_index => ns_name 344 | // See http://www.mediawiki.org/wiki/Manual:Namespace#Built-in_namespaces 345 | $namespaces = array('0' => 'Main', '1' => 'Talk'); 346 | 347 | do { 348 | $response = $this->_mediawiki->getRecentChanges( 349 | array('rcprop' => 'user|comment|timestamp|title|ids|sizes|loginfo|flags', 350 | 'rclimit' => '100', 351 | 'rcnamespace' => implode('|', array_keys($namespaces)), 352 | 'rcstart' => $start) 353 | ); 354 | 355 | foreach ($response['query']['recentchanges'] as $value) { 356 | 357 | // Extract the title, removing the namespace if any. 358 | $title = preg_replace('/^(.+:)?(.+)$/', '$2', $value['title']); 359 | 360 | // Preempt further processing on contributions with an invalid 361 | // prefix. 362 | if (Scripto_Document::BASE_TITLE_PREFIX != $title[0]) { 363 | continue; 364 | } 365 | 366 | // Set the document ID and page ID. 367 | $documentIds = Scripto_Document::decodeBaseTitle($title); 368 | 369 | // Set the document title and document page name. Reduce calls 370 | // to the adapter by caching each document title and page name, 371 | // and checking if they exist. 372 | $cachedDocument = array_key_exists($documentIds[0], $documentTitles); 373 | $cachedDocumentPage = array_key_exists($documentIds[1], $documentPageNames); 374 | 375 | // The document title and page name have been cached. 376 | if ($cachedDocument && $cachedDocumentPage) { 377 | $documentTitle = $documentTitles[$documentIds[0]]; 378 | $documentPageName = $documentPageNames[$documentIds[1]]; 379 | 380 | // The document title has been cached, but not the page name. 381 | } else if ($cachedDocument && !$cachedDocumentPage) { 382 | // Filter out invalid document pages. 383 | if (!$this->_adapter->documentPageExists($documentIds[0], $documentIds[1])) { 384 | continue; 385 | } 386 | $documentTitle = $documentTitles[$documentIds[0]]; 387 | $documentPageName = $this->_adapter->getDocumentPageName($documentIds[0], $documentIds[1]); 388 | $documentPageNames[$documentIds[1]] = $documentPageName; 389 | 390 | // The document title and page name have not been cached. 391 | } else { 392 | // Filter out invalid document pages. 393 | if (!$this->_adapter->documentPageExists($documentIds[0], $documentIds[1])) { 394 | continue; 395 | } 396 | $documentTitle = $this->_adapter->getDocumentTitle($documentIds[0]); 397 | $documentTitles[$documentIds[0]] = $documentTitle; 398 | $documentPageName = $this->_adapter->getDocumentPageName($documentIds[0], $documentIds[1]); 399 | $documentPageNames[$documentIds[1]] = $documentPageName; 400 | } 401 | 402 | $logAction = isset($value['logaction']) ? $value['logaction']: null; 403 | $action = self::getChangeAction(array('comment' => $value['comment'], 404 | 'log_action' => $logAction)); 405 | 406 | $recentChanges[] = array( 407 | 'type' => $value['type'], 408 | 'namespace_index' => $value['ns'], 409 | 'namespace_name' => $namespaces[$value['ns']], 410 | 'mediawiki_title' => $value['title'], 411 | 'rcid' => $value['rcid'], 412 | 'page_id' => $value['pageid'], 413 | 'revision_id' => $value['revid'], 414 | 'old_revision_id' => $value['old_revid'], 415 | 'user' => $value['user'], 416 | 'old_length' => $value['oldlen'], 417 | 'new_length' => $value['newlen'], 418 | 'timestamp' => $value['timestamp'], 419 | 'comment' => $value['comment'], 420 | 'action' => $action, 421 | 'log_id' => isset($value['logid']) ? $value['logid']: null, 422 | 'log_type' => isset($value['logtype']) ? $value['logtype']: null, 423 | 'log_action' => $logAction, 424 | 'new' => isset($value['new']) ? true: false, 425 | 'minor' => isset($value['minor']) ? true: false, 426 | 'document_id' => $documentIds[0], 427 | 'document_page_id' => $documentIds[1], 428 | 'document_title' => $documentTitle, 429 | 'document_page_name' => $documentPageName, 430 | ); 431 | 432 | // Break out of the loops if limit has been reached. 433 | if ($limit == count($recentChanges)) { 434 | break 2; 435 | } 436 | } 437 | 438 | // Set the query continue, if any. 439 | if (isset($response['query-continue'])) { 440 | $start = $response['query-continue']['recentchanges']['rcstart']; 441 | } else { 442 | $start = null; 443 | } 444 | 445 | } while ($start); 446 | 447 | return $recentChanges; 448 | } 449 | 450 | /** 451 | * Get the current user's watchlist. 452 | * 453 | * @link http://www.mediawiki.org/wiki/API:Watchlist 454 | * @uses Scripto_Service_MediaWiki::getWatchlist() 455 | * @param int $limit The number of recent changes to return. 456 | * @return array 457 | */ 458 | public function getWatchlist($limit = 10) 459 | { 460 | $start = null; 461 | $watchlist = array(); 462 | $documentTitles = array(); 463 | $documentPageNames = array(); 464 | 465 | // Namespaces to get: ns_index => ns_name 466 | // See http://www.mediawiki.org/wiki/Manual:Namespace#Built-in_namespaces 467 | $namespaces = array('0' => 'Main', '1' => 'Talk'); 468 | 469 | do { 470 | $response = $this->_mediawiki->getWatchlist( 471 | array('wlprop' => 'user|comment|timestamp|title|ids|sizes|flags', 472 | 'wllimit' => '100', 473 | 'wlallrev' => true, 474 | 'wlnamespace' => implode('|', array_keys($namespaces)), 475 | 'wlstart' => $start) 476 | ); 477 | 478 | foreach ($response['query']['watchlist'] as $value) { 479 | 480 | // Extract the title, removing the namespace if any. 481 | $title = preg_replace('/^(.+:)?(.+)$/', '$2', $value['title']); 482 | 483 | // Preempt further processing on contributions with an invalid 484 | // prefix. 485 | if (Scripto_Document::BASE_TITLE_PREFIX != $title[0]) { 486 | continue; 487 | } 488 | 489 | // Set the document ID and page ID. 490 | $documentIds = Scripto_Document::decodeBaseTitle($title); 491 | 492 | // Set the document title and document page name. Reduce calls 493 | // to the adapter by caching each document title and page name, 494 | // and checking if they exist. 495 | $cachedDocument = array_key_exists($documentIds[0], $documentTitles); 496 | $cachedDocumentPage = array_key_exists($documentIds[1], $documentPageNames); 497 | 498 | // The document title and page name have been cached. 499 | if ($cachedDocument && $cachedDocumentPage) { 500 | $documentTitle = $documentTitles[$documentIds[0]]; 501 | $documentPageName = $documentPageNames[$documentIds[1]]; 502 | 503 | // The document title has been cached, but not the page name. 504 | } else if ($cachedDocument && !$cachedDocumentPage) { 505 | // Filter out invalid document pages. 506 | if (!$this->_adapter->documentPageExists($documentIds[0], $documentIds[1])) { 507 | continue; 508 | } 509 | $documentTitle = $documentTitles[$documentIds[0]]; 510 | $documentPageName = $this->_adapter->getDocumentPageName($documentIds[0], $documentIds[1]); 511 | $documentPageNames[$documentIds[1]] = $documentPageName; 512 | 513 | // The document title and page name have not been cached. 514 | } else { 515 | // Filter out invalid document pages. 516 | if (!$this->_adapter->documentPageExists($documentIds[0], $documentIds[1])) { 517 | continue; 518 | } 519 | $documentTitle = $this->_adapter->getDocumentTitle($documentIds[0]); 520 | $documentTitles[$documentIds[0]] = $documentTitle; 521 | $documentPageName = $this->_adapter->getDocumentPageName($documentIds[0], $documentIds[1]); 522 | $documentPageNames[$documentIds[1]] = $documentPageName; 523 | } 524 | 525 | $action = self::getChangeAction(array('comment' => $value['comment'], 526 | 'revision_id' => $value['revid'])); 527 | 528 | $watchlist[] = array( 529 | 'namespace_index' => $value['ns'], 530 | 'namespace_name' => $namespaces[$value['ns']], 531 | 'mediawiki_title' => $value['title'], 532 | 'page_id' => $value['pageid'], 533 | 'revision_id' => $value['revid'], 534 | 'user' => $value['user'], 535 | 'old_length' => $value['oldlen'], 536 | 'new_length' => $value['newlen'], 537 | 'timestamp' => $value['timestamp'], 538 | 'comment' => $value['comment'], 539 | 'action' => $action, 540 | 'new' => isset($value['new']) ? true: false, 541 | 'minor' => isset($value['minor']) ? true: false, 542 | 'anonymous' => isset($value['anon']) ? true: false, 543 | 'document_id' => $documentIds[0], 544 | 'document_page_id' => $documentIds[1], 545 | 'document_title' => $documentTitle, 546 | 'document_page_name' => $documentPageName, 547 | ); 548 | 549 | // Break out of the loops if limit has been reached. 550 | if ($limit == count($watchlist)) { 551 | break 2; 552 | } 553 | } 554 | 555 | // Set the query continue, if any. 556 | if (isset($response['query-continue'])) { 557 | $start = $response['query-continue']['watchlist']['wlstart']; 558 | } else { 559 | $start = null; 560 | } 561 | 562 | } while ($start); 563 | 564 | return $watchlist; 565 | } 566 | 567 | /** 568 | * Get all documents from MediaWiki that have at least one page with text. 569 | * 570 | * @uses Scripto_Service_MediaWiki::getAllPages() 571 | * @return array An array following this format: 572 | * 573 | * array( 574 | * {document ID} => array( 575 | * ['mediawiki_titles'] => array( 576 | * {page ID} => {mediawiki title}, 577 | * {...} 578 | * ), 579 | * ['document_title'] => {document title} 580 | * ), 581 | * {...} 582 | * ) 583 | * 584 | */ 585 | public function getAllDocuments() 586 | { 587 | $from = null; 588 | $documentTitles = array(); 589 | $allDocuments = array(); 590 | do { 591 | $response = $this->_mediawiki->getAllPages( 592 | array('aplimit' => 500, 593 | 'apminsize' => 1, 594 | 'apprefix' => Scripto_Document::BASE_TITLE_PREFIX, 595 | 'apfrom' => $from) 596 | ); 597 | 598 | foreach ($response['query']['allpages'] as $value) { 599 | 600 | // Set the document ID and page ID. 601 | $documentIds = Scripto_Document::decodeBaseTitle($value['title']); 602 | 603 | // Set the page and continue if the document was already set. 604 | if (array_key_exists($documentIds[0], $documentTitles)) { 605 | $allDocuments[$documentIds[0]]['mediawiki_titles'][$documentIds[1]] = $value['title']; 606 | continue; 607 | 608 | // Set the document. Before getting the title, filter out pages 609 | // that are not valid documents. 610 | } else { 611 | if (!$this->_adapter->documentExists($documentIds[0])) { 612 | continue; 613 | } 614 | $documentTitle = $this->_adapter->getDocumentTitle($documentIds[0]); 615 | $documentTitles[$documentIds[0]] = $documentTitle; 616 | } 617 | 618 | $allDocuments[$documentIds[0]] = array( 619 | 'mediawiki_titles' => array($documentIds[1] => $value['title']), 620 | 'document_title' => $documentTitle, 621 | ); 622 | } 623 | 624 | // Set the query continue, if any. 625 | if (isset($response['query-continue'])) { 626 | $from = $response['query-continue']['allpages']['apfrom']; 627 | } else { 628 | $from = null; 629 | } 630 | 631 | } while ($from); 632 | 633 | return $allDocuments; 634 | } 635 | 636 | /** 637 | * Get the difference between two page revisions. 638 | * 639 | * @uses Scripto_Service_MediaWiki::getRevisionDiff() 640 | * @param int $fromRevisionId The revision ID from which to diff. 641 | * @param int|string $toRevisionId The revision to which to diff. Use the 642 | * revision ID, "prev", "next", or "cur". 643 | * @return string An HTML table without the wrapping tag containing 644 | * difference markup, pre-formatted by MediaWiki. It is the responsibility 645 | * of implementers to wrap the result with table tags. 646 | */ 647 | public function getRevisionDiff($fromRevisionId, $toRevisionId = 'prev') 648 | { 649 | return $this->_mediawiki->getRevisionDiff($fromRevisionId, $toRevisionId); 650 | } 651 | 652 | /** 653 | * Get properties of the specified page revision. 654 | * 655 | * @uses Scripto_Service_MediaWiki::getRevisions() 656 | * @param int $revisionId The ID of the rpage evision. 657 | * @return array 658 | */ 659 | public function getRevision($revisionId) 660 | { 661 | // Get the revision properties. 662 | $response = $this->_mediawiki->getRevisions( 663 | null, 664 | array('revids' => $revisionId, 665 | 'rvprop' => 'ids|flags|timestamp|user|comment|size|content') 666 | ); 667 | $page = current($response['query']['pages']); 668 | 669 | // Parse the wikitext into HTML. 670 | $response = $this->_mediawiki->parse( 671 | array('text' => '__NOEDITSECTION__' . $page['revisions'][0]['*']) 672 | ); 673 | 674 | $action = self::getChangeAction(array('comment' => $page['revisions'][0]['comment'])); 675 | 676 | $revision = array('revision_id' => $page['revisions'][0]['revid'], 677 | 'parent_id' => $page['revisions'][0]['parentid'], 678 | 'user' => $page['revisions'][0]['user'], 679 | 'timestamp' => $page['revisions'][0]['timestamp'], 680 | 'comment' => $page['revisions'][0]['comment'], 681 | 'size' => $page['revisions'][0]['size'], 682 | 'action' => $action, 683 | 'wikitext' => $page['revisions'][0]['*'], 684 | 'html' => $response['parse']['text']['*']); 685 | return $revision; 686 | } 687 | 688 | /** 689 | * Infer a change action verb from hints containted in various responses. 690 | * 691 | * @param array $hints Keyed hints from which to infer an change action: 692 | *
      693 | *
    • comment
    • 694 | *
    • log_action
    • 695 | *
    • revision_id
    • 696 | *
    697 | * @return string 698 | */ 699 | static public function getChangeAction(array $hints = array()) 700 | { 701 | $action = ''; 702 | 703 | // Recent changes returns log_action=protect|unprotect with no comment. 704 | if (array_key_exists('log_action', $hints)) { 705 | $logActions = array('protect' => 'protected', 'unprotect' => 'unprotected'); 706 | if (array_key_exists($hints['log_action'], $logActions)) { 707 | return $logActions[$hints['log_action']]; 708 | } 709 | } 710 | 711 | // Infer from comment and revision_id. 712 | if (array_key_exists('comment', $hints)) { 713 | $commentActions = array('Replaced', 'Unprotected', 'Protected', 'Created'); 714 | $actionPattern = '/^(' . implode('|', $commentActions) . ').+$/s'; 715 | if (preg_match($actionPattern, $hints['comment'])) { 716 | $action = preg_replace_callback($actionPattern, function ($matches) { 717 | return strtolower($matches[1]); 718 | }, $hints['comment']); 719 | } else { 720 | // Watchlist returns revision_id=0 when the action is protect 721 | // or unprotect. 722 | if (array_key_exists('revision_id', $hints) && 0 == $hints['revision_id']) { 723 | $action = 'un/protected'; 724 | } else { 725 | $action = 'edited'; 726 | } 727 | } 728 | } 729 | 730 | return $action; 731 | } 732 | 733 | /** 734 | * Determine whether the provided MediaWiki API URL is valid. 735 | * 736 | * @uses Scripto_Service_MediaWiki::isValidApiUrl() 737 | * @param string $apiUrl The MediaWiki API URL to validate. 738 | * @return bool 739 | */ 740 | static public function isValidApiUrl($apiUrl) 741 | { 742 | return Scripto_Service_MediaWiki::isValidApiUrl($apiUrl); 743 | } 744 | 745 | /** 746 | * Remove all HTML attributes from the provided markup. 747 | * 748 | * This filter is useful after getting HTML from the MediaWiki API, which 749 | * often contains MediaWiki-specific attributes that may conflict with local 750 | * settings. 751 | * 752 | * @see http://www.php.net/manual/en/domdocument.loadhtml.php#95251 753 | * @param string $html 754 | * @param array $exceptions Do not remove these attributes. 755 | * @return string 756 | */ 757 | static public function removeHtmlAttributes($html, array $exceptions = array('href')) 758 | { 759 | // Check for an empty string. 760 | $html = trim($html ? $html : ''); 761 | if (empty($html)) { 762 | return $html; 763 | } 764 | 765 | // Load the HTML into DOM. Must inject an XML declaration with encoding 766 | // set to UTF-8 to prevent DOMDocument from munging Unicode characters. 767 | $doc = new DOMDocument(); 768 | $doc->loadHTML('' . $html); 769 | $xpath = new DOMXPath($doc); 770 | 771 | // Iterate over and remove attributes. 772 | foreach ($xpath->evaluate('//@*') as $attribute) { 773 | // Do not remove specified attributes. 774 | if (in_array($attribute->name, $exceptions)) { 775 | continue; 776 | } 777 | $attribute->ownerElement->removeAttributeNode($attribute); 778 | } 779 | 780 | return $doc->saveHTML(); 781 | } 782 | 783 | /** 784 | * Remove all preprocessor limit reports from the provided markup. 785 | * 786 | * This filter is useful after getting HTML from the MediaWiki API, which 787 | * always contains a preprocessor limit report within hidden tags. 788 | * 789 | * @see http://en.wikipedia.org/wiki/Wikipedia:Template_limits#How_can_you_find_out.3F 790 | * @param string $text 791 | * @return string 792 | */ 793 | static public function removeNewPPLimitReports($html) 794 | { 795 | // The "s" modifier means the "." meta-character will include newlines. 796 | // The "?" means the "+" quantifier is not greedy, thus will not remove 797 | // text between pages when importing document transcriptions. 798 | $html = preg_replace("//s", '', $html); 799 | return $html; 800 | } 801 | } 802 | -------------------------------------------------------------------------------- /libraries/Scripto/Adapter/Example.php: -------------------------------------------------------------------------------- 1 | array( 31 | * 'document_title' => {documentTitle}, 32 | * 'document_pages' => array( 33 | * {pageId} => array( 34 | * 'page_name' => {pageName}, 35 | * 'page_file_url' => {pageFileUrl} 36 | * ) 37 | * ) 38 | * ) 39 | * 40 | * Other adapters will likely get relevant data using the CMS API, and not 41 | * hardcode them like this example. Be sure to URL encode the document and 42 | * page IDs when transporting over HTTP. For example: 43 | * 44 | * documentId: Request for Purchase of Liver Oil & Drum Heads 45 | * pageId: xbe/XBE02001.jpg 46 | * ?documentId=Request+for+Purchase+of+Liver+Oil+%26+Drum+Heads&pageId=xbe%2FXBE02001.jpg 47 | * 48 | * These example documents are from Center for History and New Media Papers 49 | * of the War Department and Library of Congress American Memory. 50 | * 51 | * @var array 52 | */ 53 | private $_documents = array( 54 | // Example of the preferred way to set the document and page IDs using 55 | // unique keys. See: http://wardepartmentpapers.org/document.php?id=16344 56 | 16344 => array( 57 | 'document_title' => 'Return of articles received and expended; work done at Springfield Massachusetts armory', 58 | 'document_pages' => array( 59 | 67799 => array( 60 | 'page_name' => 'Letter Outside', 61 | 'page_file_url' => 'http://wardepartmentpapers.org/images/medium/zto/ZTO07001.jpg' 62 | ), 63 | 67800 => array( 64 | 'page_name' => 'Letter Body', 65 | 'page_file_url' => 'http://wardepartmentpapers.org/images/medium/zto/ZTO07002.jpg' 66 | ), 67 | 67801 => array( 68 | 'page_name' => 'Worksheet 1, Outside', 69 | 'page_file_url' => 'http://wardepartmentpapers.org/images/medium/zto/ZTO07003.jpg' 70 | ), 71 | 67802 => array( 72 | 'page_name' => 'Worksheet 1, Page 1', 73 | 'page_file_url' => 'http://wardepartmentpapers.org/images/medium/zto/ZTO07004.jpg' 74 | ), 75 | 67803 => array( 76 | 'page_name' => 'Worksheet 1, Page 2', 77 | 'page_file_url' => 'http://wardepartmentpapers.org/images/medium/zto/ZTO07005.jpg' 78 | ), 79 | 67804 => array( 80 | 'page_name' => 'Worksheet 2, Outside', 81 | 'page_file_url' => 'http://wardepartmentpapers.org/images/medium/zto/ZTO07006.jpg' 82 | ), 83 | 67805 => array( 84 | 'page_name' => 'Worksheet 2, Page 1', 85 | 'page_file_url' => 'http://wardepartmentpapers.org/images/medium/zto/ZTO07007.jpg' 86 | ) 87 | ) 88 | ), 89 | // An alternate way to set the document using a document title as the 90 | // document ID and the file path as the page ID. See: http://books.google.com/books?id=eAuOQMmGEYIC&lpg=PA515&ots=PtWRBKDZbf&pg=PA515 91 | // %5BFacsimile%20of%5D%20letter%20to%20Messrs.%20O.%20P.%20Hall%20et%20al%20from%20Lincoln. 92 | '[Facsimile of] letter to Messrs. O. P. Hall et al from Lincoln.' => array( 93 | 'document_title' => '[Facsimile of] letter to Messrs. O. P. Hall et al from Lincoln.', 94 | 'document_pages' => array( 95 | // rbc%2Flprbscsm%2Fscsm0455%2F001r.jpg 96 | 'rbc/lprbscsm/scsm0455/001r.jpg' => array( 97 | 'page_name' => '001r', 98 | 'page_file_url' => 'http://memory.loc.gov/service/rbc/lprbscsm/scsm0455/001r.jpg' 99 | ), 100 | 'rbc/lprbscsm/scsm0455/002r.jpg' => array( 101 | 'page_name' => '002r', 102 | 'page_file_url' => 'http://memory.loc.gov/service/rbc/lprbscsm/scsm0455/002r.jpg' 103 | ), 104 | 'rbc/lprbscsm/scsm0455/003r.jpg' => array( 105 | 'page_name' => '003r', 106 | 'page_file_url' => 'http://memory.loc.gov/service/rbc/lprbscsm/scsm0455/003r.jpg' 107 | ), 108 | 'rbc/lprbscsm/scsm0455/004r.jpg' => array( 109 | 'page_name' => '004r', 110 | 'page_file_url' => 'http://memory.loc.gov/service/rbc/lprbscsm/scsm0455/004r.jpg' 111 | ) 112 | ) 113 | ) 114 | ); 115 | 116 | public function documentExists($documentId) 117 | { 118 | return array_key_exists($documentId, $this->_documents); 119 | } 120 | 121 | public function documentPageExists($documentId, $pageId) 122 | { 123 | if (!array_key_exists($documentId, $this->_documents)) { 124 | return false; 125 | } 126 | return array_key_exists($pageId, $this->_documents[$documentId]['document_pages']); 127 | } 128 | 129 | public function getDocumentPages($documentId) 130 | { 131 | if (!array_key_exists($documentId, $this->_documents)) { 132 | throw new Scripto_Adapter_Exception('Document does not exist.'); 133 | } 134 | $pages = array(); 135 | foreach ($this->_documents[$documentId]['document_pages'] as $pageId => $page) { 136 | $pages[$pageId] = $page['page_name']; 137 | } 138 | return $pages; 139 | } 140 | 141 | public function getDocumentPageFileUrl($documentId, $pageId) 142 | { 143 | if (!array_key_exists($documentId, $this->_documents)) { 144 | throw new Scripto_Adapter_Exception('Document does not exist.'); 145 | } 146 | if (!array_key_exists($pageId, $this->_documents[$documentId]['document_pages'])) { 147 | throw new Scripto_Adapter_Exception('Document page does not exist.'); 148 | } 149 | return $this->_documents[$documentId]['document_pages'][$pageId]['page_file_url']; 150 | } 151 | 152 | public function getDocumentFirstPageId($documentId) 153 | { 154 | if (!array_key_exists($documentId, $this->_documents)) { 155 | throw new Scripto_Adapter_Exception('Document does not exist.'); 156 | } 157 | reset($this->_documents[$documentId]['document_pages']); 158 | return key($this->_documents[$documentId]['document_pages']); 159 | } 160 | 161 | public function getDocumentTitle($documentId) 162 | { 163 | if (!array_key_exists($documentId, $this->_documents)) { 164 | throw new Scripto_Adapter_Exception('Document does not exist.'); 165 | } 166 | return $this->_documents[$documentId]['document_title']; 167 | } 168 | 169 | public function getDocumentPageName($documentId, $pageId) 170 | { 171 | if (!array_key_exists($documentId, $this->_documents)) { 172 | throw new Scripto_Adapter_Exception('Document does not exist.'); 173 | } 174 | if (!array_key_exists($pageId, $this->_documents[$documentId]['document_pages'])) { 175 | throw new Scripto_Adapter_Exception('Document page does not exist.'); 176 | } 177 | return $this->_documents[$documentId]['document_pages'][$pageId]['page_name']; 178 | } 179 | 180 | public function documentTranscriptionIsImported($documentId) 181 | { 182 | return false; 183 | } 184 | 185 | public function documentPageTranscriptionIsImported($documentId, $pageId) 186 | { 187 | return false; 188 | } 189 | 190 | public function importDocumentPageTranscription($documentId, $pageId, $text) 191 | { 192 | return false; 193 | } 194 | 195 | public function importDocumentTranscription($documentId, $text) 196 | { 197 | return false; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /libraries/Scripto/Adapter/Exception.php: -------------------------------------------------------------------------------- 1 | [pageName], [...]) 54 | * 55 | * Example return values: 56 | * array(2011 => 'Title Page', 57 | * 1999 => 'Page 1', 58 | * 4345 => 'Page 2') 59 | * 60 | * array('page_1' => 1, 61 | * 'page_2' => 2, 62 | * 'page_3' => 3) 63 | * 64 | * @param int|string $documentId The unique document ID 65 | * @return array An array containing page identifiers as keys and page names 66 | * as values, in sequential page order. 67 | */ 68 | public function getDocumentPages($documentId); 69 | 70 | /** 71 | * Get the URL of the specified document page file. 72 | * 73 | * @param int|string $documentId The unique document ID 74 | * @param int|string $pageId The unique page ID 75 | * @return string The page file URL 76 | */ 77 | public function getDocumentPageFileUrl($documentId, $pageId); 78 | 79 | /** 80 | * Get the first page of the document. 81 | * 82 | * @param int|string $documentId The document ID 83 | * @return int|string 84 | */ 85 | public function getDocumentFirstPageId($documentId); 86 | 87 | /** 88 | * Get the title of the document. 89 | * 90 | * @param int|string $documentId The document ID 91 | * @return string 92 | */ 93 | public function getDocumentTitle($documentId); 94 | 95 | /** 96 | * Get the name of the document page. 97 | * 98 | * @param int|string $documentId The document ID 99 | * @param int|string $pageId The unique page ID 100 | * @return string 101 | */ 102 | public function getDocumentPageName($documentId, $pageId); 103 | 104 | /** 105 | * Indicate whether the document transcription has been imported. 106 | * 107 | * @param int|string $documentId The document ID 108 | * @return bool True: has been imported; false: has not been imported 109 | */ 110 | public function documentTranscriptionIsImported($documentId); 111 | 112 | /** 113 | * Indicate whether the document page transcription has been imported. 114 | * 115 | * @param int|string $documentId The document ID 116 | * @param int|string $pageId The page ID 117 | */ 118 | public function documentPageTranscriptionIsImported($documentId, $pageId); 119 | 120 | /** 121 | * Import a document page's transcription into the external system. 122 | * 123 | * @param int|string $documentId The document ID 124 | * @param int|string $pageId The page ID 125 | * @param string $text The text to import 126 | * @return bool True: success; false: fail 127 | */ 128 | public function importDocumentPageTranscription($documentId, $pageId, $text); 129 | 130 | /** 131 | * Import an entire document's transcription into the external system. 132 | * 133 | * @param int|string The document ID 134 | * @param string The text to import 135 | * @return bool True: success; false: fail 136 | */ 137 | public function importDocumentTranscription($documentId, $text); 138 | } 139 | -------------------------------------------------------------------------------- /libraries/Scripto/Document.php: -------------------------------------------------------------------------------- 1 | documentExists($id)) { 108 | throw new Scripto_Exception("The specified document does not exist: {$this->_id}"); 109 | } 110 | 111 | $this->_id = $id; 112 | $this->_adapter = $adapter; 113 | $this->_mediawiki = $mediawiki; 114 | $this->_title = $this->_adapter->getDocumentTitle($id); 115 | } 116 | 117 | /** 118 | * Set the current document page. 119 | * 120 | * Sets the current page ID, the base title used by MediaWiki, and 121 | * information about the MediaWiki transcription and talk pages. 122 | * 123 | * @param string|null $pageId The unique page identifier. 124 | */ 125 | public function setPage($pageId) 126 | { 127 | // Set to the first page if the provided page is NULL or FALSE. 128 | if (null === $pageId || false === $pageId) { 129 | $pageId = $this->getFirstPageId(); 130 | } 131 | 132 | // Check if the page exists. 133 | if (!$this->_adapter->documentPageExists($this->_id, $pageId)) { 134 | throw new Scripto_Exception("The specified page does not exist: $pageId"); 135 | } 136 | 137 | // Mint the page title used by MediaWiki. 138 | $baseTitle = self::encodeBaseTitle($this->_id, $pageId); 139 | 140 | // Check if the base title is under the maximum character length. 141 | if (self::TITLE_BYTE_LIMIT < strlen($baseTitle)) { 142 | throw new Scripto_Exception('The document ID and/or page ID are too long to set the provided page.'); 143 | } 144 | 145 | // Set information about the transcription and talk pages. 146 | $this->_transcriptionPageInfo = $this->_getPageInfo($baseTitle); 147 | $this->_talkPageInfo = $this->_getPageInfo('Talk:' . $baseTitle); 148 | 149 | $this->_pageId = $pageId; 150 | $this->_pageName = $this->_adapter->getDocumentPageName($this->_id, $pageId); 151 | $this->_baseTitle = $baseTitle; 152 | } 153 | 154 | /** 155 | * Get this document's ID. 156 | * 157 | * @return string|int 158 | */ 159 | public function getId() 160 | { 161 | return $this->_id; 162 | } 163 | 164 | /** 165 | * Get this document's title. 166 | */ 167 | public function getTitle() 168 | { 169 | return $this->_title; 170 | } 171 | 172 | /** 173 | * Get this document page's name. 174 | */ 175 | public function getPageName() 176 | { 177 | return $this->_pageName; 178 | } 179 | 180 | /** 181 | * Get this document's current page ID. 182 | * 183 | * @return string|int 184 | */ 185 | public function getPageId() 186 | { 187 | return $this->_pageId; 188 | } 189 | 190 | /** 191 | * Get this document's current base title. 192 | * 193 | * @return string 194 | */ 195 | public function getBaseTitle() 196 | { 197 | if (is_null($this->_pageId)) { 198 | throw new Scripto_Exception('The document page must be set before getting the base title.'); 199 | } 200 | return $this->_baseTitle; 201 | } 202 | 203 | /** 204 | * Get information about the current MediaWiki transcription page. 205 | * 206 | * @return array 207 | */ 208 | public function getTranscriptionPageInfo() 209 | { 210 | if (is_null($this->_pageId)) { 211 | throw new Scripto_Exception('The document page must be set before getting information about the transcription page.'); 212 | } 213 | return $this->_transcriptionPageInfo; 214 | } 215 | 216 | /** 217 | * Get information about the current MediaWiki talk page. 218 | * 219 | * @return array 220 | */ 221 | public function getTalkPageInfo() 222 | { 223 | if (is_null($this->_pageId)) { 224 | throw new Scripto_Exception('The document page must be set before getting information about the talk page.'); 225 | } 226 | return $this->_talkPageInfo; 227 | } 228 | 229 | /** 230 | * Get all of this document's pages from the adapter. 231 | * 232 | * @uses Scripto_Adapter_Interface::getDocumentPages() 233 | * @return array 234 | */ 235 | public function getPages() 236 | { 237 | return (array) $this->_adapter->getDocumentPages($this->_id); 238 | } 239 | 240 | /** 241 | * Get this document's first page ID from the adapter. 242 | * 243 | * @uses Scripto_Adapter_Interface::getDocumentFirstPageId() 244 | * @return array 245 | */ 246 | public function getFirstPageId() 247 | { 248 | return $this->_adapter->getDocumentFirstPageId($this->_id); 249 | } 250 | 251 | /** 252 | * Get this document's current page file URL from the adapter. 253 | * 254 | * @uses Scripto_Adapter_Interface::getDocumentPageFileUrl() 255 | * @return string 256 | */ 257 | public function getPageFileUrl() 258 | { 259 | if (is_null($this->_pageId)) { 260 | throw new Scripto_Exception('The document page must be set before getting the page file URL.'); 261 | } 262 | return $this->_adapter->getDocumentPageFileUrl($this->_id, $this->_pageId); 263 | } 264 | 265 | /** 266 | * Get the MediaWiki URL for the current transcription page. 267 | * 268 | * @return string 269 | */ 270 | public function getTranscriptionPageMediawikiUrl() 271 | { 272 | if (is_null($this->_pageId)) { 273 | throw new Scripto_Exception('The document page must be set before getting the transcription page MediaWiki URL.'); 274 | } 275 | return $this->_getPageMediawikiUrl($this->_baseTitle); 276 | } 277 | 278 | /** 279 | * Get the MediaWiki URL for the current talk page. 280 | * 281 | * @return string 282 | */ 283 | public function getTalkPageMediawikiUrl() 284 | { 285 | if (is_null($this->_pageId)) { 286 | throw new Scripto_Exception('The document page must be set before getting the talk page MediaWiki URL.'); 287 | } 288 | return $this->_getPageMediawikiUrl('Talk:' . $this->_baseTitle); 289 | } 290 | 291 | /** 292 | * Get the MediaWiki transcription page wikitext for the current page. 293 | * 294 | * @uses Scripto_Service_MediaWiki::getLatestRevisionWikitext() 295 | * @return string The transcription wikitext. 296 | */ 297 | public function getTranscriptionPageWikitext() 298 | { 299 | if (is_null($this->_pageId)) { 300 | throw new Scripto_Exception('The document page must be set before getting the transcription page wikitext.'); 301 | } 302 | return $this->_mediawiki->getLatestRevisionWikitext($this->_baseTitle); 303 | } 304 | 305 | /** 306 | * Get the MediaWiki talk page wikitext for the current page. 307 | * 308 | * @uses Scripto_Service_MediaWiki::getLatestRevisionWikitext() 309 | * @return string The talk wikitext. 310 | */ 311 | public function getTalkPageWikitext() 312 | { 313 | if (is_null($this->_pageId)) { 314 | throw new Scripto_Exception('The document page must be set before getting the talk page wikitext.'); 315 | } 316 | return $this->_mediawiki->getLatestRevisionWikitext('Talk:' . $this->_baseTitle); 317 | } 318 | 319 | /** 320 | * Get the MediaWiki transcription page HTML for the current page. 321 | * 322 | * @uses Scripto_Service_MediaWiki::getLatestRevisionHtml() 323 | * @return string The transcription HTML. 324 | */ 325 | public function getTranscriptionPageHtml() 326 | { 327 | if (is_null($this->_pageId)) { 328 | throw new Scripto_Exception('The document page must be set before getting the transcription page HTML.'); 329 | } 330 | return $this->_mediawiki->getLatestRevisionHtml($this->_baseTitle); 331 | } 332 | 333 | /** 334 | * Get the MediaWiki talk page HTML for the current page. 335 | * 336 | * @uses Scripto_Service_MediaWiki::getLatestRevisionHtml() 337 | * @return string The talk HTML. 338 | */ 339 | public function getTalkPageHtml() 340 | { 341 | if (is_null($this->_pageId)) { 342 | throw new Scripto_Exception('The document page must be set before getting the talk page HTML.'); 343 | } 344 | return $this->_mediawiki->getLatestRevisionHtml('Talk:' . $this->_baseTitle); 345 | } 346 | 347 | /** 348 | * Get the MediaWiki transcription page plain text for the current page. 349 | * 350 | * @uses Scripto_Service_MediaWiki::getLatestRevisionHtml() 351 | * @return string The transcription page plain text. 352 | */ 353 | public function getTranscriptionPagePlainText() 354 | { 355 | if (is_null($this->_pageId)) { 356 | throw new Scripto_Exception('The document page must be set before getting the transcription page plain text.'); 357 | } 358 | return html_entity_decode(strip_tags($this->_mediawiki->getLatestRevisionHtml($this->_baseTitle))); 359 | } 360 | 361 | /** 362 | * Get the MediaWiki talk plain text for the current page. 363 | * 364 | * @uses Scripto_Service_MediaWiki::getLatestRevisionHtml() 365 | * @return string The talk plain text. 366 | */ 367 | public function getTalkPagePlainText() 368 | { 369 | if (is_null($this->_pageId)) { 370 | throw new Scripto_Exception('The document page must be set before getting the talk page plain text.'); 371 | } 372 | return html_entity_decode(strip_tags($this->_mediawiki->getLatestRevisionHtml('Talk:' . $this->_baseTitle))); 373 | } 374 | 375 | /** 376 | * Get the MediaWiki transcription page revision history for the current page. 377 | * 378 | * @param int $limit The number of revisions to return. 379 | * @param int $startRevisionId The revision ID from which to start. 380 | * @return array 381 | */ 382 | public function getTranscriptionPageHistory($limit = 10, $startRevisionId = null) 383 | { 384 | if (is_null($this->_pageId)) { 385 | throw new Scripto_Exception('The document page must be set before getting the transcription page history.'); 386 | } 387 | return $this->_getPageHistory($this->_baseTitle, $limit, $startRevisionId); 388 | } 389 | 390 | /** 391 | * Get the MediaWiki talk page revision history for the current page. 392 | * 393 | * @param int $limit The number of revisions to return. 394 | * @param int $startRevisionId The revision ID from which to start. 395 | * @return array 396 | */ 397 | public function getTalkPageHistory($limit = 10, $startRevisionId = null) 398 | { 399 | if (is_null($this->_pageId)) { 400 | throw new Scripto_Exception('The document page must be set before getting the talk page history.'); 401 | } 402 | return $this->_getPageHistory('Talk:' . $this->_baseTitle, $limit, $startRevisionId); 403 | } 404 | 405 | /** 406 | * Determine if the current user can edit the MediaWiki transcription page. 407 | * 408 | * @return bool 409 | */ 410 | public function canEditTranscriptionPage() 411 | { 412 | if (is_null($this->_pageId)) { 413 | throw new Scripto_Exception('The document page must be set before determining whether the user can edit the transcription page.'); 414 | } 415 | return $this->_canEdit($this->_transcriptionPageInfo['protections']); 416 | } 417 | 418 | /** 419 | * Determine if the current user can edit the MediaWiki talk page. 420 | * 421 | * @return bool 422 | */ 423 | public function canEditTalkPage() 424 | { 425 | if (is_null($this->_pageId)) { 426 | throw new Scripto_Exception('The document page must be set before determining whether the user can edit the talk page.'); 427 | } 428 | return $this->_canEdit($this->_talkPageInfo['protections']); 429 | } 430 | 431 | /** 432 | * Edit the MediaWiki transcription page for the current document. 433 | * 434 | * @uses Scripto_Service_MediaWiki::edit() 435 | * @param string $text The wikitext of the transcription. 436 | */ 437 | public function editTranscriptionPage($text) 438 | { 439 | if (is_null($this->_pageId)) { 440 | throw new Scripto_Exception('The document page must be set before editing the transcription page.'); 441 | } 442 | $this->_mediawiki->edit($this->_baseTitle, $text); 443 | } 444 | 445 | /** 446 | * Edit the MediaWiki talk page for the current document. 447 | * 448 | * @uses Scripto_Service_MediaWiki::edit() 449 | * @param string $text The wikitext of the transcription. 450 | */ 451 | public function editTalkPage($text) 452 | { 453 | if (is_null($this->_pageId)) { 454 | throw new Scripto_Exception('The document page must be set before editing the talk page.'); 455 | } 456 | $this->_mediawiki->edit('Talk:' . $this->_baseTitle, $text); 457 | } 458 | 459 | /** 460 | * Protect the current transcription page. 461 | */ 462 | public function protectTranscriptionPage() 463 | { 464 | if (is_null($this->_pageId)) { 465 | throw new Scripto_Exception('The document page must be set before protecting the transcription page.'); 466 | } 467 | $this->_protectPage($this->_baseTitle, null); 468 | 469 | // Update information about this page. 470 | $this->_transcriptionPageInfo = $this->_getPageInfo($this->_baseTitle); 471 | } 472 | 473 | /** 474 | * Protect the current talk page. 475 | */ 476 | public function protectTalkPage() 477 | { 478 | if (is_null($this->_pageId)) { 479 | throw new Scripto_Exception('The document page must be set before protecting the talk page.'); 480 | } 481 | $this->_protectPage('Talk:' . $this->_baseTitle, null); 482 | 483 | // Update information about this page. 484 | $this->_talkPageInfo = $this->_getPageInfo('Talk:' . $this->_baseTitle); 485 | } 486 | 487 | /** 488 | * Unprotect the current transcription page. 489 | */ 490 | public function unprotectTranscriptionPage() 491 | { 492 | if (is_null($this->_pageId)) { 493 | throw new Scripto_Exception('The document page must be set before unprotecting the transcription page.'); 494 | } 495 | $this->_unprotectPage($this->_baseTitle, null); 496 | 497 | // Update information about this page. 498 | $this->_transcriptionPageInfo = $this->_getPageInfo($this->_baseTitle); 499 | } 500 | 501 | /** 502 | * Unprotect the current talk page. 503 | */ 504 | public function unprotectTalkPage() 505 | { 506 | if (is_null($this->_pageId)) { 507 | throw new Scripto_Exception('The document page must be set before unprotecting the talk page.'); 508 | } 509 | $this->_unprotectPage('Talk:' . $this->_baseTitle, null); 510 | 511 | // Update information about this page. 512 | $this->_talkPageInfo = $this->_getPageInfo('Talk:' . $this->_baseTitle); 513 | } 514 | 515 | /** 516 | * Watch the current page. 517 | * 518 | * Watching a transcription page implies watching its talk page. 519 | * 520 | * @uses Scripto_Service_MediaWiki::watch() 521 | */ 522 | public function watchPage() 523 | { 524 | if (is_null($this->_pageId)) { 525 | throw new Scripto_Exception('The document page must be set before watching the page.'); 526 | } 527 | $this->_mediawiki->watch($this->_baseTitle); 528 | } 529 | 530 | /** 531 | * Unwatch the current page. 532 | * 533 | * Unwatching a transcription page implies unwatching its talk page. 534 | * 535 | * @uses Scripto_Service_MediaWiki::watch() 536 | */ 537 | public function unwatchPage() 538 | { 539 | if (is_null($this->_pageId)) { 540 | throw new Scripto_Exception('The document page must be set before unwatching the page.'); 541 | } 542 | $this->_mediawiki->watch($this->_baseTitle, null, array('unwatch' => true)); 543 | } 544 | 545 | /** 546 | * Determine whether the current transcription page is edit protected. 547 | * 548 | * @return bool 549 | */ 550 | public function isProtectedTranscriptionPage() 551 | { 552 | if (is_null($this->_pageId)) { 553 | throw new Scripto_Exception('The document page must be set before determining whether the transcription page is protected.'); 554 | } 555 | return $this->_isProtectedPage($this->_transcriptionPageInfo['protections']); 556 | } 557 | 558 | /** 559 | * Determine whether the current talk page is edit protected. 560 | * 561 | * @return bool 562 | */ 563 | public function isProtectedTalkPage() 564 | { 565 | if (is_null($this->_pageId)) { 566 | throw new Scripto_Exception('The document page must be set before determining whether the talk page is protected.'); 567 | } 568 | return $this->_isProtectedPage($this->_talkPageInfo['protections']); 569 | } 570 | 571 | /** 572 | * Determine whether the current user is watching the current page. 573 | * 574 | * @return bool 575 | */ 576 | public function isWatchedPage() 577 | { 578 | if (is_null($this->_pageId)) { 579 | throw new Scripto_Exception('The document page must be set before determining whether the current user is watching the page.'); 580 | } 581 | return $this->_transcriptionPageInfo['watched']; 582 | } 583 | 584 | /** 585 | * Determine whether all of this document's transcription pages were already 586 | * exported to the external system. 587 | * 588 | * @uses Scripto_Adapter_Interface::documentTranscriptionIsImported() 589 | * @return bool 590 | */ 591 | public function isExported() 592 | { 593 | return $this->_adapter->documentTranscriptionIsImported($this->_id); 594 | } 595 | 596 | /** 597 | * Determine whether the current transcription page was already exported to 598 | * the external system. 599 | * 600 | * @uses Scripto_Adapter_Interface::documentPageTranscriptionIsImported() 601 | * @return bool 602 | */ 603 | public function isExportedPage() 604 | { 605 | if (is_null($this->_pageId)) { 606 | throw new Scripto_Exception('The document page must be set before determining whether it is imported.'); 607 | } 608 | return $this->_adapter->documentPageTranscriptionIsImported($this->_id, $this->_pageId); 609 | } 610 | 611 | /** 612 | * Export the document page transcription to the external system by calling 613 | * the adapter. 614 | * 615 | * @uses Scripto_Adapter_Interface::importDocumentPageTranscription() 616 | * @param string $type The type of text to set, valid options are 617 | * plain_text, html, and wikitext. 618 | */ 619 | public function exportPage($type = 'plain_text') 620 | { 621 | switch ($type) { 622 | case 'plain_text': 623 | $text = $this->getTranscriptionPagePlainText(); 624 | break; 625 | case 'html': 626 | $text = $this->getTranscriptionPageHtml(); 627 | break; 628 | case 'wikitext': 629 | $text = $this->getTranscriptionPageWikitext(); 630 | break; 631 | default: 632 | throw new Scripto_Exception('The provided import type is invalid.'); 633 | } 634 | $this->_adapter->importDocumentPageTranscription($this->_id, 635 | $this->_pageId, 636 | trim($text)); 637 | } 638 | 639 | /** 640 | * Export the entire document transcription to the external system by 641 | * calling the adapter. 642 | * 643 | * @uses Scripto_Adapter_Interface::importDocumentTranscription() 644 | * @param string $type The type of text to set, valid options are 645 | * plain_text, html, and wikitext. 646 | * @param string $pageDelimiter The delimiter used to stitch pages together. 647 | */ 648 | public function export($type = 'plain_text', $pageDelimiter = "\n") 649 | { 650 | $text = array(); 651 | foreach ($this->getPages() as $pageId => $pageName) { 652 | $baseTitle = self::encodeBaseTitle($this->_id, $pageId); 653 | switch ($type) { 654 | case 'plain_text': 655 | $text[] = html_entity_decode(strip_tags($this->_mediawiki->getLatestRevisionHtml($baseTitle))); 656 | break; 657 | case 'html': 658 | $text[] = $this->_mediawiki->getLatestRevisionHtml($baseTitle); 659 | break; 660 | case 'wikitext': 661 | $text[] = $this->_mediawiki->getLatestRevisionWikitext($baseTitle); 662 | break; 663 | default: 664 | throw new Scripto_Exception('The provided import type is invalid.'); 665 | } 666 | } 667 | $text = implode($pageDelimiter, array_map('trim', $text)); 668 | $this->_adapter->importDocumentTranscription($this->_id, trim($text)); 669 | } 670 | 671 | /** 672 | * Determine if the current user can edit the specified MediaWiki page. 673 | * 674 | * @uses Scripto_Service_MediaWiki::getUserInfo() 675 | * @param array $pageProtections 676 | * @return bool 677 | */ 678 | protected function _canEdit(array $pageProtections) 679 | { 680 | $userInfo = $this->_mediawiki->getUserInfo('rights'); 681 | 682 | // Users without edit rights cannot edit pages. 683 | if (!in_array('edit', $userInfo['query']['userinfo']['rights'])) { 684 | return false; 685 | } 686 | 687 | // Users with edit rights can edit unprotected pages. 688 | if (empty($pageProtections)) { 689 | return true; 690 | } 691 | 692 | // Iterate the page protections. 693 | foreach ($pageProtections as $pageProtection) { 694 | 695 | // The page is edit-protected. 696 | if ('edit' == $pageProtection['type']) { 697 | 698 | // Users with edit and protect rights can edit protected pages. 699 | if (in_array('protect', $userInfo['query']['userinfo']['rights'])) { 700 | return true; 701 | 702 | // Users with edit but without protect rights cannot edit 703 | // protected pages. 704 | } else { 705 | return false; 706 | } 707 | } 708 | } 709 | 710 | // Users with edit rights can edit pages that are not edit-protected. 711 | return true; 712 | } 713 | 714 | /** 715 | * Determine whether the provided protections contain an edit protection. 716 | * 717 | * @param array $pageProtections The page protections from the page info: 718 | * {@link Scripto_Document::$_transcriptionPageInfo} or 719 | * {@link Scripto_Document::$_talkPageInfo}. 720 | * @return bool 721 | */ 722 | protected function _isProtectedPage(array $pageProtections) 723 | { 724 | // There are no protections. 725 | if (empty($pageProtections)) { 726 | return false; 727 | } 728 | 729 | // Iterate the page protections. 730 | foreach ($pageProtections as $pageProtection) { 731 | // The page is edit protected. 732 | if ('edit' == $pageProtection['type'] || 'create' == $pageProtection['type']) { 733 | return true; 734 | } 735 | } 736 | 737 | // There are no edit protections. 738 | return false; 739 | } 740 | 741 | /** 742 | * Protect the specified page. 743 | * 744 | * @uses Scripto_Service_MediaWiki::protect() 745 | * @param string $title 746 | * @param string $protectToken 747 | */ 748 | protected function _protectPage($title, $protectToken) 749 | { 750 | if ($this->_mediawiki->pageCreated($title)) { 751 | $protections = 'edit=sysop'; 752 | } else { 753 | $protections = 'create=sysop'; 754 | } 755 | $this->_mediawiki->protect($title, $protections, $protectToken); 756 | } 757 | 758 | /** 759 | * Unprotect the specified page. 760 | * 761 | * @uses Scripto_Service_MediaWiki::protect() 762 | * @param string $title 763 | * @param string $protectToken 764 | */ 765 | protected function _unprotectPage($title, $protectToken) 766 | { 767 | if ($this->_mediawiki->pageCreated($title)) { 768 | $protections = 'edit=all'; 769 | } else { 770 | $protections = 'create=all'; 771 | } 772 | $this->_mediawiki->protect($title, $protections, $protectToken); 773 | } 774 | 775 | /** 776 | * Get the MediaWiki URL for the specified page. 777 | * 778 | * @uses Scripto_Service_MediaWiki::getSiteInfo() 779 | * @param string $title 780 | * @return string 781 | */ 782 | protected function _getPageMediawikiUrl($title) 783 | { 784 | $siteInfo = $this->_mediawiki->getSiteInfo(); 785 | return $siteInfo['query']['general']['server'] 786 | . str_replace('$1', $title, $siteInfo['query']['general']['articlepath']); 787 | } 788 | 789 | /** 790 | * Get information for the specified page. 791 | * 792 | * @uses Scripto_Service_MediaWiki::getInfo() 793 | * @param string $title 794 | * @return array 795 | */ 796 | protected function _getPageInfo($title) 797 | { 798 | $params = array('inprop' => 'protection|talkid|subjectid|url|watched'); 799 | $response = $this->_mediawiki->getInfo($title, $params); 800 | $page = current($response['query']['pages']); 801 | $pageInfo = array('page_id' => isset($page['pageid']) ? $page['pageid'] : null, 802 | 'namespace_index' => isset($page['ns']) ? $page['ns'] : null, 803 | 'mediawiki_title' => isset($page['title']) ? $page['title'] : null, 804 | 'last_revision_id' => isset($page['lastrevid']) ? $page['lastrevid'] : null, 805 | 'counter' => isset($page['counter']) ? $page['counter'] : null, 806 | 'length' => isset($page['length']) ? $page['length'] : null, 807 | 'start_timestamp' => isset($page['starttimestamp']) ? $page['starttimestamp'] : null, 808 | 'protections' => isset($page['protection']) ? $page['protection'] : null, 809 | 'talk_id' => isset($page['talkid']) ? $page['talkid'] : null, 810 | 'mediawiki_full_url' => isset($page['fullurl']) ? $page['fullurl'] : null, 811 | 'mediawiki_edit_url' => isset($page['editurl']) ? $page['editurl'] : null, 812 | 'watched' => isset($page['watched']) ? true: false, 813 | 'redirect' => isset($page['redirect']) ? true: false, 814 | 'new' => isset($page['new']) ? true: false); 815 | return $pageInfo; 816 | } 817 | 818 | /** 819 | * Get the revisions for the specified page. 820 | * 821 | * @uses Scripto_Service_MediaWiki::getRevisions() 822 | * @param string $title 823 | * @param int $limit 824 | * @param int $startRevisionId 825 | * @return array 826 | */ 827 | protected function _getPageHistory($title, $limit = 10, $startRevisionId = null) 828 | { 829 | $revisions = array(); 830 | do { 831 | $response = $this->_mediawiki->getRevisions( 832 | $title, 833 | array('rvstartid' => $startRevisionId, 834 | 'rvlimit' => 100, 835 | 'rvprop' => 'ids|flags|timestamp|user|comment|size') 836 | ); 837 | $page = current($response['query']['pages']); 838 | 839 | // Return if the page has not been created. 840 | if (array_key_exists('missing', $page)) { 841 | return $revisions; 842 | } 843 | 844 | foreach ($page['revisions'] as $revision) { 845 | 846 | $action = Scripto::getChangeAction(array('comment' => $revision['comment'])); 847 | 848 | // Build the revisions. 849 | $revisions[] = array( 850 | 'revision_id' => $revision['revid'], 851 | 'parent_id' => $revision['parentid'], 852 | 'user' => $revision['user'], 853 | 'timestamp' => $revision['timestamp'], 854 | 'comment' => $revision['comment'], 855 | 'size' => $revision['size'], 856 | 'action' => $action, 857 | ); 858 | 859 | // Break out of the loops if limit has been reached. 860 | if ($limit == count($revisions)) { 861 | break 2; 862 | } 863 | } 864 | 865 | // Set the query continue, if any. 866 | if (isset($response['query-continue'])) { 867 | $startRevisionId = $response['query-continue']['revisions']['rvstartid']; 868 | } else { 869 | $startRevisionId = null; 870 | } 871 | 872 | } while ($startRevisionId); 873 | 874 | return $revisions; 875 | } 876 | 877 | /** 878 | * Encode a base title that enables fail-safe document page transport 879 | * between the external system, Scripto, and MediaWiki. 880 | * 881 | * The base title is the base MediaWiki page title that corresponds to the 882 | * document page. Encoding is necessary to allow all Unicode characters in 883 | * document and page IDs, even those not allowed in URL syntax and MediaWiki 884 | * naming conventions. Encoding in Base64 allows the title to be decoded. 885 | * 886 | * The base title has four parts: 887 | *
      888 | *
    1. A title prefix to keep MediaWiki from capitalizing the first 889 | * character
    2. 890 | *
    3. A URL-safe Base64 encoded document ID
    4. 891 | *
    5. A delimiter between the encoded document ID and page ID
    6. 892 | *
    7. A URL-safe Base64 encoded page ID
    8. 893 | *
    894 | * 895 | * @link http://en.wikipedia.org/wiki/Base64#URL_applications 896 | * @link http://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_%28technical_restrictions%29 897 | * @param string|int $documentId The document ID 898 | * @param string|int $pageId The page ID 899 | * @return string The encoded base title 900 | */ 901 | static public function encodeBaseTitle($documentId, $pageId) 902 | { 903 | return self::BASE_TITLE_PREFIX 904 | . Scripto_Document::base64UrlEncode($documentId) 905 | . self::BASE_TITLE_DELIMITER 906 | . Scripto_Document::base64UrlEncode($pageId); 907 | } 908 | 909 | /** 910 | * Decode the base title. 911 | * 912 | * @param string|int $baseTitle 913 | * @return array An array containing the document ID and page ID 914 | */ 915 | static public function decodeBaseTitle($baseTitle) 916 | { 917 | // First remove the title prefix. 918 | $baseTitle = ltrim($baseTitle, self::BASE_TITLE_PREFIX); 919 | // Create an array containing the document ID and page ID. 920 | $baseTitle = explode(self::BASE_TITLE_DELIMITER, $baseTitle); 921 | // URL-safe Base64 decode the array and return it. 922 | return array_map('Scripto_Document::base64UrlDecode', $baseTitle); 923 | } 924 | 925 | /** 926 | * Encode a string to URL-safe Base64. 927 | * 928 | * @link http://en.wikipedia.org/wiki/Base64#URL_applications 929 | * @param string $str 930 | * @return string 931 | */ 932 | static public function base64UrlEncode($str) 933 | { 934 | return strtr(rtrim(base64_encode($str), '='), '+/', '-_'); 935 | } 936 | 937 | /** 938 | * Decode a string from a URL-safe Base64. 939 | * 940 | * @param string $str 941 | * @return string 942 | */ 943 | static public function base64UrlDecode($str) 944 | { 945 | return base64_decode(strtr($str, '-_', '+/')); 946 | } 947 | } 948 | -------------------------------------------------------------------------------- /libraries/Scripto/Exception.php: -------------------------------------------------------------------------------- 1 | array( 55 | 'text', 'title', 'page', 'prop', 'pst', 'uselang' 56 | ), 57 | 'edit' => array( 58 | 'title', 'section', 'text', 'token', 'summary', 'minor', 'notminor', 59 | 'bot', 'basetimestamp', 'starttimestamp', 'recreate', 'createonly', 60 | 'nocreate', 'watchlist', 'md5', 'captchaid', 'captchaword', 'undo', 61 | 'undoafter' 62 | ), 63 | 'protect' => array( 64 | 'title', 'token', 'protections', 'expiry', 'reason', 'cascade' 65 | ), 66 | 'watch' => array( 67 | 'title', 'unwatch', 'token' 68 | ), 69 | 'query' => array( 70 | // title specifications 71 | 'titles', 'revids', 'pageids', 72 | // submodules 73 | 'meta', 'prop', 'list', 'type', 74 | // meta submodule 75 | 'siprop', 'sifilteriw', 'sishowalldb', 'sinumberingroup', 76 | 'uiprop', 77 | // prop submodule 78 | 'inprop', 'intoken', 'indexpageids', 'incontinue', 79 | 'rvprop', 'rvcontinue', 'rvlimit', 'rvstartid', 'rvendid', 80 | 'rvstart', 'rvend', 'rvdir', 'rvuser', 'rvexcludeuser', 81 | 'rvexpandtemplates', 'rvgeneratexml', 'rvsection', 'rvtoken', 82 | 'rvdiffto', 'rvdifftotext', 83 | // list submodule 84 | 'ucprop', 'ucuser', 'ucuserprefix', 'ucstart', 'ucend', 85 | 'uccontinue', 'ucdir', 'uclimit', 'ucnamespace', 'ucshow', 86 | 'rcprop', 'rcstart', 'rcend', 'rcdir', 'rclimit', 'rcnamespace', 87 | 'rcuser', 'rcexcludeuser', 'rctype', 'rcshow', 88 | 'wlprop', 'wlstart', 'wlend', 'wldir', 'wllimit', 'wlnamespace', 89 | 'wluser', 'wlexcludeuser', 'wlowner', 'wltoken', 'wlallrev', 90 | 'wlshow', 91 | 'aplimit', 'apminsize', 'apmaxsize', 'apprefix', 'apfrom', 92 | 'apnamespace', 'apfilterredir', 'apfilterlanglinks', 'apprtype', 93 | 'apprlevel', 'apdir', 94 | ), 95 | 'login' => array( 96 | 'lgname', 'lgpassword', 'lgtoken' 97 | ), 98 | 'logout' => array('token') 99 | ); 100 | 101 | /** 102 | * Constructs the MediaWiki API client. 103 | * 104 | * @link http://www.mediawiki.org/wiki/API:Main_page 105 | * @param string $apiUrl The URL to the MediaWiki API. 106 | * @param bool $passCookies Pass cookies to the web browser. 107 | * @param string $cookiePrefix 108 | */ 109 | public function __construct($apiUrl, $passCookies = true, $cookiePrefix = null) 110 | { 111 | $this->_passCookies = (bool) $passCookies; 112 | 113 | if (null !== $cookiePrefix) { 114 | $this->_cookiePrefix = $cookiePrefix; 115 | } elseif (isset($_COOKIE[self::COOKIE_NS . 'cookieprefix'])) { 116 | // Set the cookie prefix that was set by MediaWiki during login. 117 | $this->_cookiePrefix = $_COOKIE[self::COOKIE_NS . 'cookieprefix']; 118 | } 119 | 120 | // Set the HTTP client for the MediaWiki API . 121 | self::getHttpClient()->setUri($apiUrl) 122 | ->setConfig(array('keepalive' => true)) 123 | ->setCookieJar(); 124 | 125 | // Add X-Forwarded-For header if applicable. 126 | if (isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['SERVER_ADDR'])) { 127 | self::getHttpClient()->setHeaders('X-Forwarded-For', 128 | $_SERVER['REMOTE_ADDR'] . ', ' . $_SERVER['SERVER_ADDR']); 129 | } 130 | 131 | // If MediaWiki API authentication cookies are being passed and the 132 | // MediaWiki cookieprefix is set, get the cookies from the browser and 133 | // add them to the HTTP client cookie jar. Doing so maintains state 134 | // between browser requests. 135 | if ($this->_passCookies && $this->_cookiePrefix) { 136 | require_once 'Zend/Http/Cookie.php'; 137 | foreach ($this->_cookieSuffixes as $cookieSuffix) { 138 | $cookieName = self::COOKIE_NS . $this->_cookiePrefix . $cookieSuffix; 139 | if (array_key_exists($cookieName, $_COOKIE)) { 140 | $cookie = new Zend_Http_Cookie($this->_cookiePrefix . $cookieSuffix, 141 | $_COOKIE[$cookieName], 142 | self::getHttpClient()->getUri()->getHost()); 143 | self::getHttpClient()->getCookieJar()->addCookie($cookie); 144 | } 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * Gets information about the current user. 151 | * 152 | * @link http://www.mediawiki.org/wiki/API:Meta#userinfo_.2F_ui 153 | * @param string $uiprop 154 | * @return array 155 | */ 156 | public function getUserInfo($uiprop = '') 157 | { 158 | $params = array('meta' => 'userinfo', 159 | 'uiprop' => $uiprop); 160 | return $this->query($params); 161 | } 162 | 163 | /** 164 | * Gets overall site information. 165 | * 166 | * @link http://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si 167 | * @param string $siprop 168 | * @return array 169 | */ 170 | public function getSiteInfo($siprop = 'general') 171 | { 172 | $params = array('meta' => 'siteinfo', 173 | 'siprop' => $siprop); 174 | return $this->query($params); 175 | } 176 | 177 | /** 178 | * Gets a list of contributions made by a given user. 179 | * 180 | * @link http://www.mediawiki.org/wiki/API:Usercontribs 181 | * @param string $ucuser 182 | * @param array $params 183 | * @return array 184 | */ 185 | public function getUserContributions($ucuser, array $params = array()) 186 | { 187 | $params['ucuser'] = $ucuser; 188 | $params['list'] = 'usercontribs'; 189 | return $this->query($params); 190 | } 191 | 192 | /** 193 | * Gets all recent changes to the wiki. 194 | * 195 | * @link http://www.mediawiki.org/wiki/API:Recentchanges 196 | * @param array $params 197 | * @return array 198 | */ 199 | public function getRecentChanges(array $params = array()) 200 | { 201 | $params['list'] = 'recentchanges'; 202 | return $this->query($params); 203 | } 204 | 205 | /** 206 | * Gets a list of pages on the current user's watchlist. 207 | * 208 | * @link http://www.mediawiki.org/wiki/API:Watchlist 209 | * @param array $params 210 | * @return array 211 | */ 212 | public function getWatchlist(array $params = array()) 213 | { 214 | $params['list'] = 'watchlist'; 215 | return $this->query($params); 216 | } 217 | 218 | /** 219 | * Gets a list of pages. 220 | * 221 | * @link http://www.mediawiki.org/wiki/API:Allpages 222 | * @param array $params 223 | * @return array 224 | */ 225 | public function getAllPages(array $params = array()) 226 | { 227 | $params['list'] = 'allpages'; 228 | return $this->query($params); 229 | } 230 | 231 | /** 232 | * Gets basic page information. 233 | * 234 | * @link http://www.mediawiki.org/wiki/API:Properties#info_.2F_in 235 | * @param string $titles 236 | * @param array $params 237 | * @return array 238 | */ 239 | public function getInfo($titles, array $params = array()) 240 | { 241 | $params['titles'] = $titles; 242 | $params['prop'] = 'info'; 243 | return $this->query($params); 244 | } 245 | 246 | /** 247 | * Gets revisions for a given page. 248 | * 249 | * @link http://www.mediawiki.org/wiki/API:Properties#revisions_.2F_rv 250 | * @param string $titles 251 | * @param array $params 252 | * @return array 253 | */ 254 | public function getRevisions($titles, array $params = array()) 255 | { 256 | $params['titles'] = $titles; 257 | $params['prop'] = 'revisions'; 258 | return $this->query($params); 259 | } 260 | 261 | /** 262 | * Gets the HTML of a specified revision of a given page. 263 | * 264 | * @param int $revisionId 265 | * @return string 266 | */ 267 | public function getRevisionHtml($revisionId) 268 | { 269 | // Get the revision wikitext. 270 | $response = $this->getRevisions(null, array('revids' => $revisionId, 271 | 'rvprop' => 'content')); 272 | $page = current($response['query']['pages']); 273 | 274 | // Parse the wikitext into HTML. 275 | $response = $this->parse( 276 | array('text' => '__NOEDITSECTION__' . $page['revisions'][0]['*']) 277 | ); 278 | return $response['parse']['text']['*']; 279 | } 280 | 281 | /** 282 | * Gets the difference between two revisions. 283 | * 284 | * @param int $from The revision ID to diff. 285 | * @param int|string $to The revision to diff to: use the revision ID, 286 | * prev, next, or cur. 287 | * @return string The API returns preformatted table rows without a wrapping 288 | *
    . Presumably this is so implementers can wrap a custom
    . 289 | */ 290 | public function getRevisionDiff($fromRevisionId, $toRevisionId = 'prev') 291 | { 292 | $response = $this->getRevisions(null, array('revids' => $fromRevisionId, 293 | 'rvdiffto' => $toRevisionId)); 294 | $page = current($response['query']['pages']); 295 | return $page['revisions'][0]['diff']['*']; 296 | } 297 | 298 | /** 299 | * Gets the edit token for a given page. 300 | * 301 | * @link http://www.mediawiki.org/wiki/API:Edit#Token 302 | * @param string $title 303 | * @return string 304 | */ 305 | public function getEditToken($title) 306 | { 307 | $response = $this->query(['meta' => 'tokens', 'type' => 'csrf']); 308 | return $response['query']['tokens']['csrftoken']; 309 | } 310 | 311 | /** 312 | * Gets the protect token for a given page. 313 | * 314 | * @link http://www.mediawiki.org/wiki/API:Protect#Token 315 | * @param string $title 316 | * @return string 317 | */ 318 | public function getProtectToken($title) 319 | { 320 | $response = $this->query(['meta' => 'tokens', 'type' => 'csrf']); 321 | return $response['query']['tokens']['csrftoken']; 322 | } 323 | 324 | /** 325 | * Gets the watch token for a given page. 326 | * 327 | * @link http://www.mediawiki.org/wiki/API:Watch#Token 328 | * @param string $title 329 | * @return string 330 | */ 331 | public function getWatchToken($title) 332 | { 333 | $response = $this->query(['meta' => 'tokens', 'type' => 'watch']); 334 | return $response['query']['tokens']['watchtoken']; 335 | } 336 | 337 | /** 338 | * Gets the protections for a given page. 339 | * 340 | * @link http://www.mediawiki.org/wiki/API:Properties#info_.2F_in 341 | * @param string $title 342 | * @return array 343 | */ 344 | public function getPageProtections($title) 345 | { 346 | $response = $this->getInfo($title, array('inprop' => 'protection')); 347 | $page = current($response['query']['pages']); 348 | return $page['protection']; 349 | } 350 | 351 | /** 352 | * Gets the wikitext of the latest revision of a given page. 353 | * 354 | * @link http://www.mediawiki.org/wiki/API:Properties#revisions_.2F_rv 355 | * @param string $title 356 | * @return string|null 357 | */ 358 | public function getLatestRevisionWikitext($title) 359 | { 360 | $response = $this->getRevisions($title, array('rvprop' => 'content', 361 | 'rvlimit' => '1')); 362 | $page = current($response['query']['pages']); 363 | 364 | // Return the wikitext only if the page already exists. 365 | $wikitext = null; 366 | if (isset($page['revisions'][0]['*'])) { 367 | $wikitext = $page['revisions'][0]['*']; 368 | } 369 | return $wikitext; 370 | } 371 | 372 | /** 373 | * Gets the HTML of the latest revision of a given page. 374 | * 375 | * @link http://www.mediawiki.org/wiki/API:Parsing_wikitext#parse 376 | * @param string $title 377 | * @return string|null 378 | */ 379 | public function getLatestRevisionHtml($title) 380 | { 381 | // To exclude [edit] links in the parsed wikitext, we must use the 382 | // following hack. 383 | $response = $this->parse(array('text' => '__NOEDITSECTION__{{:' . $title . '}}')); 384 | 385 | // Return the text only if the page already exists. Otherwise, the 386 | // returned HTML is a link to the document's MediaWiki edit page. The 387 | // only indicator I found in the response XML is the "exists" attribute 388 | // in the templates node; but this may not be adequate. 389 | $html = null; 390 | if (isset($response['parse']['templates'][0]['exists'])) { 391 | $html = $response['parse']['text']['*']; 392 | } 393 | return $html; 394 | } 395 | 396 | /** 397 | * Get the HTML preview of the given text. 398 | * 399 | * @link http://www.mediawiki.org/wiki/API:Parsing_wikitext#parse 400 | * @param string $text 401 | * @return string 402 | */ 403 | public function getPreview($text) 404 | { 405 | $response = $this->parse(array('text' => '__NOEDITSECTION__' . $text)); 406 | return $response['parse']['text']['*']; 407 | } 408 | 409 | /** 410 | * Returns whether a given page is created. 411 | * 412 | * @link http://www.mediawiki.org/wiki/API:Query#Missing_and_invalid_titles 413 | * @param string $title 414 | * @return bool 415 | */ 416 | public function pageCreated($title) 417 | { 418 | $response = $this->query(array('titles' => $title)); 419 | $page = current($response['query']['pages']); 420 | if (isset($page['missing']) || isset($page['invalid'])) { 421 | return false; 422 | } 423 | return true; 424 | } 425 | 426 | /** 427 | * Returns parsed wikitext. 428 | * 429 | * @link http://www.mediawiki.org/wiki/API:Parsing_wikitext#parse 430 | * @param array $params 431 | * @return array 432 | */ 433 | public function parse(array $params = array()) 434 | { 435 | return $this->_request('parse', $params); 436 | } 437 | 438 | /** 439 | * Returns data. 440 | * 441 | * @link http://www.mediawiki.org/wiki/API:Query 442 | * @param array $params 443 | * @return array 444 | */ 445 | public function query(array $params = array()) 446 | { 447 | return $this->_request('query', $params); 448 | } 449 | 450 | /** 451 | * Watch or unwatch pages. 452 | * 453 | * @link http://www.mediawiki.org/wiki/API:Watch 454 | * @param string $title 455 | * @param array $params 456 | * @return array 457 | */ 458 | public function watch($title, $watchtoken = null, array $params = array()) 459 | { 460 | // Get the watch token if not passed. 461 | if (is_null($watchtoken)) { 462 | $watchtoken = $this->getWatchToken($title); 463 | } 464 | $params['title'] = $title; 465 | $params['token'] = $watchtoken; 466 | return $this->_request('watch', $params); 467 | } 468 | 469 | /** 470 | * Applies protections to a given page. 471 | * 472 | * @link http://www.mediawiki.org/wiki/API:Protect 473 | * @param string $title 474 | * @param string $protections 475 | * @param string|null $protecttokens 476 | * @param array $params 477 | * @return array 478 | */ 479 | public function protect($title, 480 | $protections, 481 | $protecttoken = null, 482 | array $params = array()) 483 | { 484 | // Get the protect token if not passed. 485 | if (is_null($protecttoken)) { 486 | $protecttoken = $this->getProtectToken($title); 487 | } 488 | 489 | // Apply protections. 490 | $params['title'] = $title; 491 | $params['protections'] = $protections; 492 | $params['token'] = $protecttoken; 493 | 494 | return $this->_request('protect', $params); 495 | } 496 | 497 | /** 498 | * Create or edit a given page. 499 | * 500 | * @link http://www.mediawiki.org/wiki/API:Edit 501 | * @link http://www.mediawiki.org/wiki/Manual:Preventing_access#Restrict_editing_of_all_pages 502 | * @param string $title 503 | * @param string $text 504 | * @param string|null $edittoken 505 | * @param array $params 506 | * @return array 507 | */ 508 | public function edit($title, 509 | $text, 510 | $edittoken = null, 511 | array $params = array()) 512 | { 513 | // Get the edit token if not passed. 514 | if (is_null($edittoken)) { 515 | $edittoken = $this->getEditToken($title); 516 | } 517 | 518 | // Protect against edit conflicts by getting the timestamp of the last 519 | // revision. 520 | $response = $this->getRevisions($title); 521 | $page = current($response['query']['pages']); 522 | 523 | $basetimestamp = null; 524 | if (isset($page['revisions'])) { 525 | $basetimestamp = $page['revisions'][0]['timestamp']; 526 | } 527 | 528 | // Edit the page. 529 | $params['title'] = $title; 530 | $params['text'] = $text; 531 | $params['token'] = $edittoken; 532 | $params['basetimestamp'] = $basetimestamp; 533 | 534 | return $this->_request('edit', $params); 535 | } 536 | 537 | /** 538 | * Login to MediaWiki. 539 | * 540 | * @link http://www.mediawiki.org/wiki/API:Login 541 | * @param string $lgname 542 | * @param string $lgpassword 543 | */ 544 | public function login($lgname, $lgpassword) 545 | { 546 | // Log in or get the login token. 547 | $params = array('lgname' => $lgname, 'lgpassword' => $lgpassword); 548 | $response = $this->_request('login', $params); 549 | 550 | // Confirm the login token. 551 | if ('NeedToken' == $response['login']['result']) { 552 | $params['lgtoken'] = $response['login']['token']; 553 | $response = $this->_request('login', $params); 554 | } 555 | 556 | // Process a successful login. 557 | if ('Success' == $response['login']['result']) { 558 | if ($this->_passCookies) { 559 | $cookiePrefix = isset($response['login']['cookieprefix']) 560 | ? $response['login']['cookieprefix'] 561 | : $this->_cookiePrefix; 562 | // Persist the MediaWiki cookie prefix in the browser. Set to 563 | // expire in 30 days, the same as MediaWiki cookies. 564 | setcookie(self::COOKIE_NS . 'cookieprefix', 565 | $cookiePrefix, 566 | time() + 60 * 60 * 24 * 30, 567 | '/'); 568 | 569 | // Persist MediaWiki authentication cookies in the browser. 570 | foreach (self::getHttpClient()->getCookieJar()->getAllCookies() as $cookie) { 571 | setcookie(self::COOKIE_NS . $this->_cookiePrefix . $cookie->getName(), 572 | $cookie->getValue(), 573 | $cookie->getExpiryTime(), 574 | '/'); 575 | } 576 | } 577 | return; 578 | } 579 | 580 | // Process an unsuccessful login. 581 | $errors = array('NoName' => 'Username is empty.', 582 | 'Illegal' => 'Username is illegal.', 583 | 'NotExists' => 'Username is not found.', 584 | 'EmptyPass' => 'Password is empty.', 585 | 'WrongPass' => 'Password is incorrect.', 586 | 'WrongPluginPass' => 'Password is incorrect (via plugin)', 587 | 'CreateBlocked' => 'IP address is blocked for account creation.', 588 | 'Throttled' => 'Login attempt limit surpassed.', 589 | 'Blocked' => 'User is blocked.'); 590 | $error = $response['login']['result']; 591 | if (array_key_exists($error, $errors)) { 592 | throw new Scripto_Service_Exception($errors[$error]); 593 | } 594 | throw new Scripto_Service_Exception('Unknown login error: ' . $response['login']['result']); 595 | } 596 | 597 | /** 598 | * Logout of MediaWiki. 599 | * 600 | * @link http://www.mediawiki.org/wiki/API:Logout 601 | */ 602 | public function logout() 603 | { 604 | // As of A CSRF token is required to log out as of MW 1.34.0. 605 | // @see https://github.com/wikimedia/mediawiki-api-demos/issues/118 606 | $response = $this->query(['meta' => 'tokens']); 607 | $token = $response['query']['tokens']['csrftoken']; 608 | 609 | // Log out. 610 | $this->_request('logout', ['token' => $token]); 611 | 612 | // Reset the cookie jar. 613 | self::getHttpClient()->getCookieJar()->reset(); 614 | 615 | if ($this->_passCookies && $this->_cookiePrefix) { 616 | // Delete the MediaWiki authentication cookies from the browser. 617 | setcookie(self::COOKIE_NS . 'cookieprefix', false, 0, '/'); 618 | foreach ($this->_cookieSuffixes as $cookieSuffix) { 619 | $cookieName = self::COOKIE_NS . $this->_cookiePrefix . $cookieSuffix; 620 | if (array_key_exists($cookieName, $_COOKIE)) { 621 | setcookie($cookieName, false, 0, '/'); 622 | } 623 | } 624 | } 625 | } 626 | 627 | /** 628 | * Makes a MediaWiki API request and returns the response. 629 | * 630 | * @param string $action 631 | * @param array $params 632 | * @return array 633 | */ 634 | protected function _request($action, array $params = array()) 635 | { 636 | // Check if this action is a valid MediaWiki API action. 637 | if (!array_key_exists($action, $this->_actions)) { 638 | throw new Scripto_Service_Exception('Invalid MediaWiki API action.'); 639 | } 640 | 641 | // Set valid parameters for this action. 642 | foreach ($params as $paramName => $paramValue) { 643 | if (in_array($paramName, $this->_actions[$action])) { 644 | self::getHttpClient()->setParameterPost($paramName, $paramValue); 645 | } 646 | } 647 | 648 | // Set default parameters. 649 | self::getHttpClient()->setParameterPost('format', 'json') 650 | ->setParameterPost('action', $action); 651 | 652 | // Get the response body and reset the request. 653 | $body = self::getHttpClient()->request('POST')->getBody(); 654 | self::getHttpClient()->resetParameters(); 655 | 656 | // Parse the response body, throwing errors when encountered. 657 | $response = json_decode($body, true); 658 | if (isset($response['error'])) { 659 | throw new Scripto_Service_Exception($response['error']['info']); 660 | } 661 | return $response; 662 | } 663 | 664 | /** 665 | * Determine whether the provided MediaWiki API URL is valid. 666 | * 667 | * @param string $apiUrl 668 | * @return bool 669 | */ 670 | static public function isValidApiUrl($apiUrl) 671 | { 672 | // Check for valid API URL string. 673 | if (!Zend_Uri::check($apiUrl) || !preg_match('#/api\.php$#', $apiUrl)) { 674 | return false; 675 | } 676 | 677 | try { 678 | // Ping the API endpoint for a valid response. 679 | $body = self::getHttpClient()->setUri($apiUrl) 680 | ->setParameterPost('action', 'query') 681 | ->setParameterPost('meta', 'siteinfo') 682 | ->setParameterPost('format', 'json') 683 | ->request('POST')->getBody(); 684 | // Prevent "Unable to Connect" errors. 685 | } catch (Zend_Http_Client_Exception $e) { 686 | return false; 687 | } 688 | self::getHttpClient()->resetParameters(true); 689 | 690 | $response = json_decode($body, true); 691 | if (!is_array($response) || !isset($response['query']['general'])) { 692 | return false; 693 | } 694 | 695 | return true; 696 | } 697 | } 698 | -------------------------------------------------------------------------------- /models/ScriptoAdapterOmeka.php: -------------------------------------------------------------------------------- 1 | _db = get_db(); 18 | } 19 | 20 | /** 21 | * Indicate whether the document exists in Omeka. 22 | * 23 | * @param int|string $documentId The unique document ID 24 | * @return bool True: it exists; false: it does not exist 25 | */ 26 | public function documentExists($documentId) 27 | { 28 | return $this->_validDocument($this->_getItem($documentId)); 29 | } 30 | 31 | /** 32 | * Indicate whether the document page exists in Omeka. 33 | * 34 | * @param int|string $documentId The unique document ID 35 | * @param int|string $pageId The unique page ID 36 | * @return bool True: it exists; false: it does not exist 37 | */ 38 | public function documentPageExists($documentId, $pageId) 39 | { 40 | $item = $this->_getItem($documentId); 41 | if (false == $this->_validDocument($item)) { 42 | return false; 43 | } 44 | // The Omeka file ID must match the Scripto page ID. 45 | $files = $item->Files; 46 | foreach ($files as $file) { 47 | if ($pageId == $file->id) { 48 | return true; 49 | } 50 | } 51 | return false; 52 | } 53 | 54 | /** 55 | * Get all the pages belonging to the document. 56 | * 57 | * @param int|string $documentId The unique document ID 58 | * @return array An array containing page identifiers as keys and page names 59 | * as values, in sequential page order. 60 | */ 61 | public function getDocumentPages($documentId) 62 | { 63 | $item = $this->_getItem($documentId); 64 | $documentPages = array(); 65 | foreach ($item->Files as $file) { 66 | // The page name is either the Dublin Core title of the file or the 67 | // file's original filename. 68 | $titles = $file->getElementTexts('Dublin Core', 'Title'); 69 | if (empty($titles)) { 70 | $pageName = $file->original_filename; 71 | } else { 72 | $pageName = $titles[0]->text; 73 | } 74 | $documentPages[$file->id] = $pageName; 75 | } 76 | return $documentPages; 77 | } 78 | 79 | /** 80 | * Get the URL of the specified document page file. 81 | * 82 | * @param int|string $documentId The unique document ID 83 | * @param int|string $pageId The unique page ID 84 | * @return string The page file URL 85 | */ 86 | public function getDocumentPageFileUrl($documentId, $pageId) 87 | { 88 | $file = $this->_getFile($pageId); 89 | return $file->getWebPath('original'); 90 | } 91 | 92 | /** 93 | * Get the first page of the document. 94 | * 95 | * @param int|string $documentId The document ID 96 | * @return int|string 97 | */ 98 | public function getDocumentFirstPageId($documentId) 99 | { 100 | $item = $this->_getItem($documentId); 101 | return $item->Files[0]->id; 102 | } 103 | 104 | /** 105 | * Get the title of the document. 106 | * 107 | * @param int|string $documentId The document ID 108 | * @return string 109 | */ 110 | public function getDocumentTitle($documentId) 111 | { 112 | $item = $this->_getItem($documentId); 113 | $titles = $item->getElementTexts('Dublin Core', 'Title'); 114 | if (empty($titles)) { 115 | return ''; 116 | } 117 | return $titles[0]->text; 118 | } 119 | 120 | /** 121 | * Get the name of the document page. 122 | * 123 | * @param int|string $documentId The document ID 124 | * @param int|string $pageId The unique page ID 125 | * @return string 126 | */ 127 | public function getDocumentPageName($documentId, $pageId) 128 | { 129 | $file = $this->_getFile($pageId); 130 | 131 | // The page name is either the Dublin Core title of the file or the 132 | // file's original filename. 133 | $titles = $file->getElementTexts('Dublin Core', 'Title'); 134 | if (empty($titles)) { 135 | $pageName = $file->original_filename; 136 | } else { 137 | $pageName = $titles[0]->text; 138 | } 139 | return $pageName; 140 | } 141 | 142 | /** 143 | * Indicate whether the document transcription has been imported. 144 | * 145 | * @param int|string $documentId The document ID 146 | * @return bool True: has been imported; false: has not been imported 147 | */ 148 | public function documentTranscriptionIsImported($documentId) 149 | {} 150 | 151 | /** 152 | * Indicate whether the document page transcription has been imported. 153 | * 154 | * @param int|string $documentId The document ID 155 | * @param int|string $pageId The page ID 156 | */ 157 | public function documentPageTranscriptionIsImported($documentId, $pageId) 158 | {} 159 | 160 | /** 161 | * Import a document page's transcription into Omeka. 162 | * 163 | * @param int|string $documentId The document ID 164 | * @param int|string $pageId The page ID 165 | * @param string $text The text to import 166 | * @return bool True: success; false: fail 167 | */ 168 | public function importDocumentPageTranscription($documentId, $pageId, $text) 169 | { 170 | $file = $this->_getFile($pageId); 171 | $element = $file->getElement('Scripto', 'Transcription'); 172 | $file->deleteElementTextsByElementId(array($element->id)); 173 | $isHtml = false; 174 | if ('html' == get_option('scripto_import_type')) { 175 | $isHtml = true; 176 | } 177 | $text = Scripto::removeNewPPLimitReports($text); 178 | $file->addTextForElement($element, $text, $isHtml); 179 | $file->save(); 180 | } 181 | 182 | /** 183 | * Import an entire document's transcription into Omeka. 184 | * 185 | * @param int|string The document ID 186 | * @param string The text to import 187 | * @return bool True: success; false: fail 188 | */ 189 | public function importDocumentTranscription($documentId, $text) 190 | { 191 | $item = $this->_getItem($documentId); 192 | $element = $item->getElement('Scripto', 'Transcription'); 193 | $item->deleteElementTextsByElementId(array($element->id)); 194 | $isHtml = false; 195 | if ('html' == get_option('scripto_import_type')) { 196 | $isHtml = true; 197 | } 198 | $text = Scripto::removeNewPPLimitReports($text); 199 | $item->addTextForElement($element, $text, $isHtml); 200 | $item->save(); 201 | } 202 | 203 | /** 204 | * Return an Omeka item object. 205 | * 206 | * @param int $itemId 207 | * @return Item|null 208 | */ 209 | private function _getItem($itemId) 210 | { 211 | return $this->_db->getTable('Item')->find($itemId); 212 | } 213 | 214 | /** 215 | * Return an Omeka file object. 216 | * 217 | * @param int $fileId 218 | * @return File|int 219 | */ 220 | private function _getFile($fileId) 221 | { 222 | return $this->_db->getTable('File')->find($fileId); 223 | } 224 | 225 | /** 226 | * Check if the provided item exists in Omeka and is a valid Scripto 227 | * document. 228 | * 229 | * @param Item $item 230 | * @return bool 231 | */ 232 | private function _validDocument($item) 233 | { 234 | // The item must exist. 235 | if (!($item instanceof Item)) { 236 | return false; 237 | } 238 | // The item must have at least one file assigned to it. 239 | if (!isset($item->Files[0])) { 240 | return false; 241 | } 242 | return true; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /plugin.ini: -------------------------------------------------------------------------------- 1 | [info] 2 | name="Scripto" 3 | author="Roy Rosenzweig Center for History and New Media" 4 | description="Adds the ability to transcribe items using the Scripto library." 5 | license="GPLv3" 6 | link="http://omeka.org/codex/Plugins/Scripto_2.0" 7 | support_link="https://forum.omeka.org/c/omeka-classic/plugins" 8 | version="2.5" 9 | omeka_minimum_version="2.0" 10 | omeka_target_version="2.4" 11 | tags="scripto, transcription, crowdsource, documents" 12 | -------------------------------------------------------------------------------- /routes.ini: -------------------------------------------------------------------------------- 1 | [routes] 2 | scripto_action.route = "scripto/:action" 3 | scripto_action.defaults.module = "scripto" 4 | scripto_action.defaults.controller = "index" 5 | 6 | scripto_action_item.route = "scripto/:action/:item-id" 7 | scripto_action_item.defaults.module = "scripto" 8 | scripto_action_item.defaults.controller = "index" 9 | scripto_action_item.reqs.item-id = "\d+" 10 | 11 | scripto_action_item_file.route = "scripto/:action/:item-id/:file-id" 12 | scripto_action_item_file.defaults.module = "scripto" 13 | scripto_action_item_file.defaults.controller = "index" 14 | scripto_action_item_file.reqs.item-id = "\d+" 15 | scripto_action_item_file.reqs.file-id = "\d+" 16 | 17 | scripto_revision.route = "scripto/revision/:item-id/:file-id/:namespace-index/:revision-id" 18 | scripto_revision.defaults.module = "scripto" 19 | scripto_revision.defaults.controller = "index" 20 | scripto_revision.defaults.action = "revision" 21 | scripto_revision.reqs.item-id = "\d+" 22 | scripto_revision.reqs.file-id = "\d+" 23 | scripto_revision.reqs.namespace-index = "0|1" 24 | scripto_revision.reqs.revision-id = "\d+" 25 | 26 | scripto_history.route = "scripto/history/:item-id/:file-id/:namespace-index" 27 | scripto_history.defaults.module = "scripto" 28 | scripto_history.defaults.controller = "index" 29 | scripto_history.defaults.action = "history" 30 | scripto_history.reqs.item-id = "\d+" 31 | scripto_history.reqs.file-id = "\d+" 32 | scripto_history.reqs.namespace-index = "0|1" 33 | 34 | scripto_diff.route = "scripto/diff/:item-id/:file-id/:namespace-index/:old-revision-id/:revision-id" 35 | scripto_diff.defaults.module = "scripto" 36 | scripto_diff.defaults.controller = "index" 37 | scripto_diff.defaults.action = "diff" 38 | scripto_diff.reqs.item-id = "\d+" 39 | scripto_diff.reqs.file-id = "\d+" 40 | scripto_diff.reqs.namespace-index = "0|1" 41 | scripto_diff.reqs.old-revision-id = "\d+" 42 | scripto_diff.reqs.revision-id = "\d+" 43 | -------------------------------------------------------------------------------- /views/admin/plugins/scripto-config-form.php: -------------------------------------------------------------------------------- 1 |

    ', '' 7 | ); ?>

    8 | 9 |

    12 | 13 |
    14 |
    15 | 16 |
    17 |
    18 |

    ', '' 21 | ); ?>

    22 | formText( 23 | 'scripto_mediawiki_api_url', 24 | get_option('scripto_mediawiki_api_url') 25 | ); ?> 26 |
    27 |
    28 | 29 |
    30 |
    31 | 32 |
    33 |
    34 |

    ', 37 | ''); ?> 38 |

    39 | formText( 40 | 'scripto_mediawiki_cookie_prefix', 41 | get_option('scripto_mediawiki_cookie_prefix') 42 | ); ?> 43 |
    44 |
    45 | 46 |
    47 |
    48 | 49 |
    50 |
    51 |

    ', '' 55 | ); ?>

    56 | formRadio( 57 | 'scripto_image_viewer', 58 | $this->image_viewer, 59 | null, 60 | array('default' => __('Omeka default'), 61 | 'openlayers' => __('OpenLayers')), 62 | null 63 | ); ?> 64 |
    65 |
    66 | 67 |
    68 |
    69 | 70 |
    71 |
    72 |

    ', '' 77 | ); ?>

    78 | formCheckbox( 79 | 'scripto_use_google_docs_viewer', 80 | null, 81 | array('checked' => (bool) $this->use_google_docs_viewer) 82 | ); ?> 83 |
    84 |
    85 | 86 |
    87 |
    88 | 89 |
    90 |
    91 |

    97 | formRadio( 98 | 'scripto_import_type', 99 | $this->import_type, 100 | null, 101 | array('html' => __('HTML'), 102 | 'plain_text' => __('plain text')), 103 | null 104 | ); ?> 105 |
    106 |
    107 | 108 |
    109 |
    110 | 111 |
    112 |
    113 |

    120 | formTextarea( 121 | 'scripto_home_page_text', 122 | get_option('scripto_home_page_text') 123 | ); ?> 124 |
    125 |
    126 | -------------------------------------------------------------------------------- /views/shared/index/diff.php: -------------------------------------------------------------------------------- 1 | namespaceIndex) ? __('Discussion') : __('Transcription'); 4 | $title = implode(' | ', $titleArray); 5 | $head = array('title' => html_escape($title)); 6 | echo head($head); 7 | ?> 8 | 17 | 18 |

    19 | 20 |
    21 | 22 | 23 |
    24 | 25 |

    26 | scripto->isLoggedIn()): ?> 27 | ' . $this->scripto->getUserName() . ''); ?> 28 | () 29 | | 30 | 31 | 32 | 33 | | 34 | | 35 | | 36 | | 37 | | 38 |

    39 | 40 |

    doc->getTitle()): ?>doc->getTitle(); ?>

    41 |

    doc->getPageName(); ?>

    42 | 43 | 44 |
    45 | 46 | 47 | 49 | 51 | 52 | 53 | 54 | diff; ?> 55 | 56 |
    oldRevision['timestamp']), Zend_Date::DATETIME_MEDIUM)); ?>
    48 | oldRevision['action'] . ' by'); ?> oldRevision['user']; ?>
    revision['timestamp']), Zend_Date::DATETIME_MEDIUM)); ?>
    50 | revision['action'] . ' by'); ?> revision['user']; ?>
    57 |

    revision['timestamp']), Zend_Date::DATETIME_MEDIUM)); ?>

    58 |
    revision['html']; ?>
    59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /views/shared/index/history.php: -------------------------------------------------------------------------------- 1 | namespaceIndex) ? __('Discussion') : __('Transcription'); 4 | $title = implode(' | ', $titleArray); 5 | $head = array('title' => html_escape($title)); 6 | echo head($head); 7 | ?> 8 | 9 |

    10 | 11 |
    12 | 13 | 14 |
    15 | 16 |

    17 | scripto->isLoggedIn()): ?> 18 | ' . $this->scripto->getUserName() . ''); ?> 19 | () 20 | | 21 | 22 | 23 | 24 | | 25 | | 26 | | 27 | | 28 |

    29 | 30 |

    doc->getTitle()): ?>doc->getTitle(); ?>

    31 |

    doc->getPageName(); ?>

    32 | 33 | 34 | history)): ?> 35 |

    36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | history as $revision): ?> 49 | $this->doc->getId(), 51 | 'file-id' => $this->doc->getPageId(), 52 | 'namespace-index' => $this->namespaceIndex, 53 | 'old-revision-id' => $revision['revision_id'], 54 | 'revision-id' => $this->info['last_revision_id']), 55 | 'scripto_diff'); 56 | $urlPrevious = url(array('item-id' => $this->doc->getId(), 57 | 'file-id' => $this->doc->getPageId(), 58 | 'namespace-index' => $this->namespaceIndex, 59 | 'old-revision-id' => $revision['parent_id'], 60 | 'revision-id' => $revision['revision_id']), 61 | 'scripto_diff'); 62 | $urlRevert = url(array('item-id' => $this->doc->getId(), 63 | 'file-id' => $this->doc->getPageId(), 64 | 'namespace-index' => $this->namespaceIndex, 65 | 'revision-id' => $revision['revision_id']), 66 | 'scripto_revision'); 67 | ?> 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
    (info['last_revision_id']): ?> | )
    78 | 79 |
    80 |
    81 | 82 | -------------------------------------------------------------------------------- /views/shared/index/index.php: -------------------------------------------------------------------------------- 1 | html_escape(__('Scripto'))); 3 | echo head($head); 4 | ?> 5 | 6 |

    7 | 8 |
    9 | 10 | 11 |
    12 | 13 |

    14 | scripto->isLoggedIn()): ?> 15 | scripto->getUserName()); ?> 16 | () 17 | | 18 | 19 | 20 | 21 | | 22 |

    23 | 24 | 25 | scripto->isLoggedIn()): ?> 26 | homePageText): ?> 27 | homePageText ?> 28 | 29 |

    30 |

    ' . get_option('site_title') . '', 38 | '', '', 39 | '', '', 40 | '', '', 41 | '', '', 42 | '', '' 43 | ); ?>

    44 | 45 | 46 |

    47 | documentPages)): ?> 48 |

    49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | documentPages as $documentPage): ?> 60 | 'transcribe', 65 | 'item-id' => $documentPage['document_id'], 66 | 'file-id' => $documentPage['document_page_id'] 67 | ), 'scripto_action_item_file'); 68 | if (1 == $documentPage['namespace_index']) { 69 | $urlTranscribe .= '#discussion'; 70 | } else { 71 | $urlTranscribe .= '#transcription'; 72 | } 73 | 74 | // document title 75 | $documentTitle = ScriptoPlugin::truncate($documentPage['document_title'], 60, __('Untitled')); 76 | $urlItem = url(array( 77 | 'controller' => 'items', 78 | 'action' => 'show', 79 | 'id' => $documentPage['document_id'] 80 | ), 'id'); 81 | ?> 82 | 83 | 84 | 86 | 87 | 88 | 89 |
    : 85 |
    90 | 91 | 92 |
    93 |
    94 | 95 | -------------------------------------------------------------------------------- /views/shared/index/login.php: -------------------------------------------------------------------------------- 1 | html_escape($title)); 5 | echo head($head); 6 | ?> 7 | 8 |

    9 | 10 |
    11 | 12 | 13 |
    14 | 15 |

    16 |

    20 | 21 | 22 |
    23 |
    24 | 25 |
    26 | formText('scripto_mediawiki_username', null, array('size' => 18)); ?> 27 |
    28 |
    29 |
    30 | 31 |
    32 | formPassword('scripto_mediawiki_password', null, array('size' => 18)); ?> 33 |
    34 |
    35 | formHidden('scripto_redirect_url', $this->redirectUrl); ?> 36 | formSubmit('scripto_mediawiki_login', __('Login'), array('style' => 'display:inline; float:none;')); ?> 37 |
    38 |
    39 |
    40 | 41 | -------------------------------------------------------------------------------- /views/shared/index/recent-changes.php: -------------------------------------------------------------------------------- 1 | html_escape(__('Scripto'))); 3 | echo head($head); 4 | ?> 5 | 6 |

    7 | 8 |
    9 | 10 | 11 |
    12 | 13 |

    14 | scripto->isLoggedIn()): ?> 15 | ' . $this->scripto->getUserName() . ''); ?> 16 | () 17 | | 18 | 19 | 20 | 21 |

    22 | 23 | 24 |

    25 | recentChanges)): ?> 26 |

    27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 'Created', 'edit' => 'Edited'); ?> 41 | recentChanges as $recentChange): ?> 42 | $recentChange['document_id'], 46 | 'file-id' => $recentChange['document_page_id'], 47 | 'namespace-index' => $recentChange['namespace_index'], 48 | 'old-revision-id' => $recentChange['old_revision_id'], 49 | 'revision-id' => $recentChange['revision_id'], 50 | ), 'scripto_diff'); 51 | $urlHistory = url(array( 52 | 'item-id' => $recentChange['document_id'], 53 | 'file-id' => $recentChange['document_page_id'], 54 | 'namespace-index' => $recentChange['namespace_index'], 55 | ), 'scripto_history'); 56 | if ($recentChange['new'] || in_array($recentChange['action'], array('protected', 'unprotected'))) { 57 | $changes .= ' (' . __('diff') . ' | ' . __('hist') . ')'; 58 | } else { 59 | $changes .= ' (' . __('diff') . ' | ' . __('hist') . ')'; 60 | } 61 | 62 | // document page name 63 | $documentPageName = ScriptoPlugin::truncate($recentChange['document_page_name'], 30); 64 | $urlTranscribe = url(array( 65 | 'action' => 'transcribe', 66 | 'item-id' => $recentChange['document_id'], 67 | 'file-id' => $recentChange['document_page_id'] 68 | ), 'scripto_action_item_file'); 69 | if (1 == $recentChange['namespace_index']) { 70 | $urlTranscribe .= '#discussion'; 71 | } else { 72 | $urlTranscribe .= '#transcription'; 73 | } 74 | 75 | // document title 76 | $documentTitle = ScriptoPlugin::truncate($recentChange['document_title'], 30, __('Untitled')); 77 | $urlItem = url(array( 78 | 'controller' => 'items', 79 | 'action' => 'show', 80 | 'id' => $recentChange['document_id'] 81 | ), 'id'); 82 | 83 | // length changed 84 | $lengthChanged = $recentChange['new_length'] - $recentChange['old_length']; 85 | if (0 <= $lengthChanged) { 86 | $lengthChanged = "+$lengthChanged"; 87 | } 88 | ?> 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
    :
    100 | 101 |
    102 |
    103 | 104 | -------------------------------------------------------------------------------- /views/shared/index/revision.php: -------------------------------------------------------------------------------- 1 | namespaceIndex) ? __('Discussion') : __('Transcription'); 4 | $title = implode(' | ', $titleArray); 5 | $head = array('title' => html_escape($title)); 6 | echo head($head); 7 | ?> 8 | 9 |

    10 | 11 |
    12 | 13 | 14 |
    15 | 16 |

    17 | scripto->isLoggedIn()): ?> 18 | ' . $this->scripto->getUserName() . ''); ?> 19 | () 20 | | 21 | 22 | 23 | 24 | | 25 | | 26 | | 27 | | 28 | | 29 |

    30 | 31 |

    doc->getTitle()): ?>doc->getTitle(); ?>

    32 |

    doc->getPageName(); ?>

    33 | 34 | 35 | namespaceIndex && $this->doc->canEditTalkPage()): ?> 36 |
    formTextarea('scripto-page-wikitext', $this->revision['wikitext'], array('cols' => '76', 'rows' => '16', 'disabled' => 'disabled')); ?>
    37 |
    38 | formSubmit('scripto-page-revert', 'Revert to this revision', array('style' => 'display:inline; float:none;')); ?> 39 |
    40 | doc->canEditTranscriptionPage()): ?> 41 |
    formTextarea('scripto-page-wikitext', $this->revision['wikitext'], array('cols' => '76', 'rows' => '16', 'disabled' => 'disabled')); ?>
    42 |
    43 | formSubmit('scripto-page-revert', __('Revert to this revision'), array('style' => 'display:inline; float:none;')); ?> 44 |
    45 | 46 | 47 | 48 |

    revision['timestamp']), Zend_Date::DATETIME_MEDIUM), 51 | __($this->revision['action'] . ' by'), 52 | $this->revision['user'] 53 | ); ?>

    54 |
    revision['html']; ?>
    55 | 56 |
    57 |
    58 | 59 | -------------------------------------------------------------------------------- /views/shared/index/transcribe.php: -------------------------------------------------------------------------------- 1 | html_escape(implode(' | ', $titleArray))); 4 | echo head($head); 5 | ?> 6 | 7 | 8 | 331 | 332 |

    333 | 334 |
    335 | 336 | 337 |
    338 | 339 |

    340 | scripto->isLoggedIn()): ?> 341 | ' . $this->scripto->getUserName() . ''); ?> 342 | () 343 | | 344 | 345 | 346 | 347 | | 348 | | 349 | | 350 |

    351 | 352 |

    doc->getTitle()): ?>doc->getTitle(); ?>

    353 | scripto->canExport()): ?>
    formButton('scripto-transcription-document-import', __('Import document'), array('style' => 'display:inline; float:none;')); ?>
    354 |

    doc->getPageName(); ?>

    355 | 356 | 357 | file, array('imageSize' => 'fullsize')); ?> 358 | 359 | 360 |

    361 | paginationUrls['previous'])): ?>« « 362 | | paginationUrls['next'])): ?> » » 363 | | 364 |

    365 | 366 | 367 |
    368 | doc->canEditTranscriptionPage()): ?> 369 | 376 | 377 |

    378 | 379 |

    380 | doc->canEditTranscriptionPage()): ?> [] 381 | scripto->canProtect()): ?> [] 382 | []

    383 |
    384 | scripto->isLoggedIn()): ?>formButton('scripto-page-watch'); ?> 385 | scripto->canProtect()): ?>formButton('scripto-transcription-page-protect'); ?> 386 | scripto->canExport()): ?>formButton('scripto-transcription-page-import', __('Import page'), array('style' => 'display:inline; float:none;')); ?> 387 |
    388 |
    transcriptionPageHtml; ?>
    389 |
    390 | 391 | 392 |
    393 | doc->canEditTalkPage()): ?> 394 | 401 | 402 |

    403 | 404 |

    405 | doc->canEditTalkPage()): ?> [] 406 | scripto->canProtect()): ?> [] 407 | []

    408 |
    409 | scripto->canProtect()): ?>formButton('scripto-talk-page-protect'); ?> 410 |
    411 |
    talkPageHtml; ?>
    412 |
    413 | 414 |
    415 |
    416 | 417 | -------------------------------------------------------------------------------- /views/shared/index/watchlist.php: -------------------------------------------------------------------------------- 1 | html_escape(__('Scripto'))); 3 | echo head($head); 4 | ?> 5 | 6 |

    7 | 8 |
    9 | 10 | 11 |
    12 | 13 |

    14 | ' . $this->scripto->getUserName() . ''); ?> 15 | () 16 | | 17 |

    18 | 19 | 20 |

    21 | watchlist)): ?> 22 |

    23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | watchlist as $revision): ?> 37 | $revision['document_id'], 42 | 'file-id' => $revision['document_page_id'], 43 | 'namespace-index' => $revision['namespace_index'], 44 | ), 'scripto_history'); 45 | $changes .= ' (' . __('hist') . ')'; 46 | 47 | // document page name 48 | $documentPageName = ScriptoPlugin::truncate($revision['document_page_name'], 30); 49 | $urlTranscribe = url(array( 50 | 'action' => 'transcribe', 51 | 'item-id' => $revision['document_id'], 52 | 'file-id' => $revision['document_page_id'] 53 | ), 'scripto_action_item_file'); 54 | if (1 == $revision['namespace_index']) { 55 | $urlTranscribe .= '#discussion'; 56 | } else { 57 | $urlTranscribe .= '#transcription'; 58 | } 59 | 60 | // document title 61 | $documentTitle = ScriptoPlugin::truncate($revision['document_title'], 30, __('Untitled')); 62 | $urlItem = url(array( 63 | 'controller' => 'items', 64 | 'action' => 'show', 65 | 'id' => $revision['document_id'] 66 | ), 'id'); 67 | 68 | // length changed 69 | $lengthChanged = $revision['new_length'] - $revision['old_length']; 70 | if (0 <= $lengthChanged) { 71 | $lengthChanged = "+$lengthChanged"; 72 | } 73 | ?> 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
    :
    85 | 86 |
    87 |
    88 | 89 | -------------------------------------------------------------------------------- /views/shared/javascripts/img/east-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeka/plugin-Scripto/c84b77e7e5a057876b4c070aeddf7e7296ba5851/views/shared/javascripts/img/east-mini.png -------------------------------------------------------------------------------- /views/shared/javascripts/img/north-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeka/plugin-Scripto/c84b77e7e5a057876b4c070aeddf7e7296ba5851/views/shared/javascripts/img/north-mini.png -------------------------------------------------------------------------------- /views/shared/javascripts/img/south-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeka/plugin-Scripto/c84b77e7e5a057876b4c070aeddf7e7296ba5851/views/shared/javascripts/img/south-mini.png -------------------------------------------------------------------------------- /views/shared/javascripts/img/west-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeka/plugin-Scripto/c84b77e7e5a057876b4c070aeddf7e7296ba5851/views/shared/javascripts/img/west-mini.png -------------------------------------------------------------------------------- /views/shared/javascripts/img/zoom-minus-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeka/plugin-Scripto/c84b77e7e5a057876b4c070aeddf7e7296ba5851/views/shared/javascripts/img/zoom-minus-mini.png -------------------------------------------------------------------------------- /views/shared/javascripts/img/zoom-plus-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeka/plugin-Scripto/c84b77e7e5a057876b4c070aeddf7e7296ba5851/views/shared/javascripts/img/zoom-plus-mini.png -------------------------------------------------------------------------------- /views/shared/javascripts/img/zoom-world-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeka/plugin-Scripto/c84b77e7e5a057876b4c070aeddf7e7296ba5851/views/shared/javascripts/img/zoom-world-mini.png -------------------------------------------------------------------------------- /views/shared/javascripts/theme/default/style.css: -------------------------------------------------------------------------------- 1 | div.olMap { 2 | z-index: 0; 3 | padding: 0px!important; 4 | margin: 0px!important; 5 | cursor: default; 6 | } 7 | 8 | div.olMapViewport { 9 | text-align: left; 10 | } 11 | 12 | div.olLayerDiv { 13 | -moz-user-select: none; 14 | } 15 | 16 | .olLayerGoogleCopyright { 17 | left: 2px; 18 | bottom: 2px; 19 | } 20 | .olLayerGooglePoweredBy { 21 | left: 2px; 22 | bottom: 15px; 23 | } 24 | .olControlAttribution { 25 | font-size: smaller; 26 | right: 3px; 27 | bottom: 4.5em; 28 | position: absolute; 29 | display: block; 30 | } 31 | .olControlScale { 32 | right: 3px; 33 | bottom: 3em; 34 | display: block; 35 | position: absolute; 36 | font-size: smaller; 37 | } 38 | .olControlScaleLine { 39 | display: block; 40 | position: absolute; 41 | left: 10px; 42 | bottom: 15px; 43 | font-size: xx-small; 44 | } 45 | .olControlScaleLineBottom { 46 | border: solid 2px black; 47 | border-bottom: none; 48 | margin-top:-2px; 49 | text-align: center; 50 | } 51 | .olControlScaleLineTop { 52 | border: solid 2px black; 53 | border-top: none; 54 | text-align: center; 55 | } 56 | 57 | .olControlPermalink { 58 | right: 3px; 59 | bottom: 1.5em; 60 | display: block; 61 | position: absolute; 62 | font-size: smaller; 63 | } 64 | 65 | div.olControlMousePosition { 66 | bottom: 0em; 67 | right: 3px; 68 | display: block; 69 | position: absolute; 70 | font-family: Arial; 71 | font-size: smaller; 72 | } 73 | 74 | .olControlOverviewMapContainer { 75 | position: absolute; 76 | bottom: 0px; 77 | right: 0px; 78 | } 79 | 80 | .olControlOverviewMapElement { 81 | padding: 10px 18px 10px 10px; 82 | background-color: #00008B; 83 | -moz-border-radius: 1em 0 0 0; 84 | } 85 | 86 | .olControlOverviewMapMinimizeButton { 87 | right: 0px; 88 | bottom: 80px; 89 | } 90 | 91 | .olControlOverviewMapMaximizeButton { 92 | right: 0px; 93 | bottom: 80px; 94 | } 95 | 96 | .olControlOverviewMapExtentRectangle { 97 | overflow: hidden; 98 | background-image: url("img/blank.gif"); 99 | cursor: move; 100 | border: 2px dotted red; 101 | } 102 | .olControlOverviewMapRectReplacement { 103 | overflow: hidden; 104 | cursor: move; 105 | background-image: url("img/overview_replacement.gif"); 106 | background-repeat: no-repeat; 107 | background-position: center; 108 | } 109 | 110 | .olLayerGeoRSSDescription { 111 | float:left; 112 | width:100%; 113 | overflow:auto; 114 | font-size:1.0em; 115 | } 116 | .olLayerGeoRSSClose { 117 | float:right; 118 | color:gray; 119 | font-size:1.2em; 120 | margin-right:6px; 121 | font-family:sans-serif; 122 | } 123 | .olLayerGeoRSSTitle { 124 | float:left;font-size:1.2em; 125 | } 126 | 127 | .olPopupContent { 128 | padding:5px; 129 | overflow: auto; 130 | } 131 | .olControlNavToolbar { 132 | width:0px; 133 | height:0px; 134 | } 135 | .olControlNavToolbar div { 136 | display:block; 137 | width: 28px; 138 | height: 28px; 139 | top: 300px; 140 | left: 6px; 141 | position: relative; 142 | } 143 | 144 | .olControlNavigationHistory { 145 | background-image: url("img/navigation_history.png"); 146 | background-repeat: no-repeat; 147 | width: 24px; 148 | height: 24px; 149 | 150 | } 151 | .olControlNavigationHistoryPreviousItemActive { 152 | background-position: 0px 0px; 153 | } 154 | .olControlNavigationHistoryPreviousItemInactive { 155 | background-position: 0px -24px; 156 | } 157 | .olControlNavigationHistoryNextItemActive { 158 | background-position: -24px 0px; 159 | } 160 | .olControlNavigationHistoryNextItemInactive { 161 | background-position: -24px -24px; 162 | } 163 | 164 | .olControlNavToolbar .olControlNavigationItemActive { 165 | background-image: url("img/panning-hand-on.png"); 166 | background-repeat: no-repeat; 167 | } 168 | .olControlNavToolbar .olControlNavigationItemInactive { 169 | background-image: url("img/panning-hand-off.png"); 170 | background-repeat: no-repeat; 171 | } 172 | .olControlNavToolbar .olControlZoomBoxItemActive { 173 | background-image: url("img/drag-rectangle-on.png"); 174 | background-color: orange; 175 | background-repeat: no-repeat; 176 | } 177 | .olControlNavToolbar .olControlZoomBoxItemInactive { 178 | background-image: url("img/drag-rectangle-off.png"); 179 | background-repeat: no-repeat; 180 | } 181 | .olControlEditingToolbar { 182 | float:right; 183 | right: 0px; 184 | height: 30px; 185 | width: 200px; 186 | } 187 | .olControlEditingToolbar div { 188 | background-image: url("img/editing_tool_bar.png"); 189 | background-repeat: no-repeat; 190 | float:right; 191 | width: 24px; 192 | height: 24px; 193 | margin: 5px; 194 | } 195 | .olControlEditingToolbar .olControlNavigationItemActive { 196 | background-position: -103px -23px; 197 | } 198 | .olControlEditingToolbar .olControlNavigationItemInactive { 199 | background-position: -103px -0px; 200 | } 201 | .olControlEditingToolbar .olControlDrawFeaturePointItemActive { 202 | background-position: -77px -23px; 203 | } 204 | .olControlEditingToolbar .olControlDrawFeaturePointItemInactive { 205 | background-position: -77px -0px; 206 | } 207 | .olControlEditingToolbar .olControlDrawFeaturePathItemInactive { 208 | background-position: -51px 0px; 209 | } 210 | .olControlEditingToolbar .olControlDrawFeaturePathItemActive { 211 | background-position: -51px -23px; 212 | } 213 | .olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive { 214 | background-position: -26px 0px; 215 | } 216 | .olControlEditingToolbar .olControlDrawFeaturePolygonItemActive { 217 | background-position: -26px -23px ; 218 | } 219 | div.olControlSaveFeaturesItemActive { 220 | background-image: url(img/save_features_on.png); 221 | background-repeat: no-repeat; 222 | background-position: 0px 1px; 223 | } 224 | div.olControlSaveFeaturesItemInactive { 225 | background-image: url(img/save_features_off.png); 226 | background-repeat: no-repeat; 227 | background-position: 0px 1px; 228 | } 229 | 230 | .olHandlerBoxZoomBox { 231 | border: 2px solid red; 232 | position: absolute; 233 | background-color: white; 234 | opacity: 0.50; 235 | font-size: 1px; 236 | filter: alpha(opacity=50); 237 | } 238 | .olHandlerBoxSelectFeature { 239 | border: 2px solid blue; 240 | position: absolute; 241 | background-color: white; 242 | opacity: 0.50; 243 | font-size: 1px; 244 | filter: alpha(opacity=50); 245 | } 246 | 247 | .olControlPanPanel { 248 | top: 10px; 249 | left: 5px; 250 | } 251 | 252 | .olControlPanPanel div { 253 | background-image: url(img/pan-panel.png); 254 | height: 18px; 255 | width: 18px; 256 | cursor: pointer; 257 | position: absolute; 258 | } 259 | 260 | .olControlPanPanel .olControlPanNorthItemInactive { 261 | top: 0px; 262 | left: 9px; 263 | background-position: 0px 0px; 264 | } 265 | .olControlPanPanel .olControlPanSouthItemInactive { 266 | top: 36px; 267 | left: 9px; 268 | background-position: 18px 0px; 269 | } 270 | .olControlPanPanel .olControlPanWestItemInactive { 271 | position: absolute; 272 | top: 18px; 273 | left: 0px; 274 | background-position: 0px 18px; 275 | } 276 | .olControlPanPanel .olControlPanEastItemInactive { 277 | top: 18px; 278 | left: 18px; 279 | background-position: 18px 18px; 280 | } 281 | 282 | .olControlZoomPanel { 283 | top: 71px; 284 | left: 14px; 285 | } 286 | 287 | .olControlZoomPanel div { 288 | background-image: url(img/zoom-panel.png); 289 | position: absolute; 290 | height: 18px; 291 | width: 18px; 292 | cursor: pointer; 293 | } 294 | 295 | .olControlZoomPanel .olControlZoomInItemInactive { 296 | top: 0px; 297 | left: 0px; 298 | background-position: 0px 0px; 299 | } 300 | 301 | .olControlZoomPanel .olControlZoomToMaxExtentItemInactive { 302 | top: 18px; 303 | left: 0px; 304 | background-position: 0px -18px; 305 | } 306 | 307 | .olControlZoomPanel .olControlZoomOutItemInactive { 308 | top: 36px; 309 | left: 0px; 310 | background-position: 0px 18px; 311 | } 312 | 313 | .olPopupCloseBox { 314 | background: url("img/close.gif") no-repeat; 315 | cursor: pointer; 316 | } 317 | 318 | .olFramedCloudPopupContent { 319 | padding: 5px; 320 | overflow: auto; 321 | } 322 | 323 | .olControlNoSelect { 324 | -moz-user-select: none; 325 | } 326 | 327 | .olImageLoadError { 328 | background-color: pink; 329 | opacity: 0.5; 330 | filter: alpha(opacity=50); /* IE */ 331 | } 332 | 333 | /** 334 | * Cursor styles 335 | */ 336 | 337 | .olCursorWait { 338 | cursor: wait; 339 | } 340 | .olDragDown { 341 | cursor: move; 342 | } 343 | .olDrawBox { 344 | cursor: crosshair; 345 | } 346 | .olControlDragFeatureOver { 347 | cursor: move; 348 | } 349 | .olControlDragFeatureActive.olControlDragFeatureOver.olDragDown { 350 | cursor: -moz-grabbing; 351 | } 352 | 353 | /** 354 | * Layer switcher 355 | */ 356 | .olControlLayerSwitcher { 357 | position: absolute; 358 | top: 25px; 359 | right: 0px; 360 | width: 20em; 361 | font-family: sans-serif; 362 | font-weight: bold; 363 | margin-top: 3px; 364 | margin-left: 3px; 365 | margin-bottom: 3px; 366 | font-size: smaller; 367 | color: white; 368 | background-color: transparent; 369 | } 370 | 371 | .olControlLayerSwitcher .layersDiv { 372 | padding-top: 5px; 373 | padding-left: 10px; 374 | padding-bottom: 5px; 375 | padding-right: 75px; 376 | background-color: darkblue; 377 | width: 100%; 378 | height: 100%; 379 | } 380 | 381 | .olControlLayerSwitcher .layersDiv .baseLbl, 382 | .olControlLayerSwitcher .layersDiv .dataLbl { 383 | margin-top: 3px; 384 | margin-left: 3px; 385 | margin-bottom: 3px; 386 | } 387 | 388 | .olControlLayerSwitcher .layersDiv .baseLayersDiv, 389 | .olControlLayerSwitcher .layersDiv .dataLayersDiv { 390 | padding-left: 10px; 391 | } 392 | 393 | .olControlLayerSwitcher .maximizeDiv, 394 | .olControlLayerSwitcher .minimizeDiv { 395 | top: 5px; 396 | right: 0px; 397 | } 398 | --------------------------------------------------------------------------------