Index: trunk/extensions/SemanticNotifyMe/specials/SMWNotifyMe/SMW_NMSendMailAsync.php |
— | — | @@ -0,0 +1,67 @@ |
| 2 | +<?php
|
| 3 | +/*
|
| 4 | + * Created on 2009/3/25
|
| 5 | + *
|
| 6 | + * Author: Dch
|
| 7 | + */
|
| 8 | +
|
| 9 | +
|
| 10 | +//get Parameter
|
| 11 | +$wgRequestTime = microtime(true);
|
| 12 | +
|
| 13 | +/** */
|
| 14 | +# Abort if called from a web server
|
| 15 | +if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
|
| 16 | + print "This script must be run from the command line\n";
|
| 17 | + exit();
|
| 18 | +}
|
| 19 | +
|
| 20 | +
|
| 21 | +if( version_compare( PHP_VERSION, '5.0.0' ) < 0 ) {
|
| 22 | + print "Sorry! This version of MediaWiki requires PHP 5; you are running " .
|
| 23 | + PHP_VERSION . ".\n\n" .
|
| 24 | + "If you are sure you already have PHP 5 installed, it may be " .
|
| 25 | + "installed\n" .
|
| 26 | + "in a different path from PHP 4. Check with your system administrator.\n";
|
| 27 | + die( -1 );
|
| 28 | +}
|
| 29 | +
|
| 30 | +// copy from user class
|
| 31 | +function getUserNMOption( $str ) {
|
| 32 | + $options = array();
|
| 33 | + $a = explode( "\n", $str );
|
| 34 | + foreach ( $a as $s ) {
|
| 35 | + $m = array();
|
| 36 | + if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
|
| 37 | + $options[$m[1]] = $m[2];
|
| 38 | + }
|
| 39 | + }
|
| 40 | + return $options['enotifyme'];
|
| 41 | +}
|
| 42 | +
|
| 43 | +// include commandLine script which provides some basic
|
| 44 | +// methodes for maintenance scripts
|
| 45 | +$mediaWikiLocation = dirname(__FILE__) . '/../../../..';
|
| 46 | +require_once "$mediaWikiLocation/maintenance/commandLine.inc";
|
| 47 | +
|
| 48 | +$sStore = smwfGetSemanticStore();
|
| 49 | +$msgs = $sStore->getUnmailedNMMessages();
|
| 50 | +foreach($msgs as $msg) {
|
| 51 | + // send notifications by mail
|
| 52 | + $user_info = $sStore->getUserInfo($msg['user_id']);
|
| 53 | + if(($user_info->user_email != '') && getUserNMOption($user_info->user_options)) {
|
| 54 | + $name = (($user_info->user_real_name=='')?$user_info->user_name:$user_info->user_real_name);
|
| 55 | + $body = "Dear Mr./Mrs. $name,<br/>".$msg['notify'].
|
| 56 | + "<br/><br/>Sincerely yours,<br/>SMW NotifyMe Bot";
|
| 57 | +
|
| 58 | + UserMailer::send( //userMailer(
|
| 59 | + new MailAddress($user_info->user_email, $name),
|
| 60 | + new MailAddress($wgEmergencyContact, 'Admin'),
|
| 61 | + 'New SMW Notification comes, from '.$wgSitename,
|
| 62 | + $body,
|
| 63 | + new MailAddress($wgEmergencyContact, 'Admin'),
|
| 64 | + true
|
| 65 | + );
|
| 66 | + }
|
| 67 | +}
|
| 68 | +?>
|
Index: trunk/extensions/SemanticNotifyMe/specials/SMWNotifyMe/SMW_NotAjaxAccess.php |
— | — | @@ -0,0 +1,111 @@ |
| 2 | +<?php
|
| 3 | +
|
| 4 | +global $wgAjaxExportList;
|
| 5 | +global $smwgNMIP;
|
| 6 | +
|
| 7 | +require_once($smwgNMIP . '/includes/SMW_NotifyProcessor.php');
|
| 8 | +$wgAjaxExportList[] = 'smwf_nm_NotifyAccess';
|
| 9 | +
|
| 10 | +
|
| 11 | +function smwf_nm_NotifyAccess($method, $params) {
|
| 12 | + $p_array = explode(",", $params);
|
| 13 | + global $smwgQEnabled;
|
| 14 | +
|
| 15 | + $result="Query disabled.";
|
| 16 | + if($method == "updateMail"){
|
| 17 | + global $wgUser;
|
| 18 | + $wgUser->setOption( 'enotifyme', $params );
|
| 19 | + $wgUser->saveSettings();
|
| 20 | + return 'Update NotifyMe mail setting successfully!';
|
| 21 | + }
|
| 22 | + else if($method == "addNotify"){
|
| 23 | + if ($smwgQEnabled) {
|
| 24 | + $result = SMWNotifyProcessor::addNotify(str_replace('&', '&', str_replace(',', ',', $p_array[0])),
|
| 25 | + str_replace('&', '&', str_replace(',', ',', $p_array[3])),
|
| 26 | + $p_array[1], $p_array[2], implode(",", array_slice($p_array, 4)));
|
| 27 | + }
|
| 28 | + return $result;
|
| 29 | + }
|
| 30 | + else if($method == "getQueryResult"){
|
| 31 | + if ($smwgQEnabled) {
|
| 32 | + $params .= '
|
| 33 | +| format=table
|
| 34 | +| link=all';
|
| 35 | +
|
| 36 | + // parse params and answer query
|
| 37 | + SMWQueryProcessor::processFunctionParams(SMWNotifyProcessor::getQueryRawParams($params),$querystring,$params,$printouts);
|
| 38 | +
|
| 39 | + $result = SMWQueryProcessor::getResultFromQueryString($querystring,$params,$printouts, SMW_OUTPUT_WIKI);
|
| 40 | + switch ($params->format) {
|
| 41 | + case 'timeline':
|
| 42 | + return $result;
|
| 43 | + break;
|
| 44 | + case 'eventline':
|
| 45 | + return $result;
|
| 46 | + break;
|
| 47 | + case 'googlepie':
|
| 48 | + return $result[0];
|
| 49 | + break;
|
| 50 | + case 'googlebar':
|
| 51 | + return $result[0];
|
| 52 | + break;
|
| 53 | + case 'exhibit':
|
| 54 | + return $result;
|
| 55 | + break;
|
| 56 | + default:
|
| 57 | + }
|
| 58 | + global $wgParser;
|
| 59 | +
|
| 60 | + if ( ($wgParser->getTitle() instanceof Title) && ($wgParser->getOptions() instanceof ParserOptions) ) {
|
| 61 | + $result = $wgParser->recursiveTagParse($result);
|
| 62 | + } else {
|
| 63 | + global $wgTitle;
|
| 64 | + $popt = new ParserOptions();
|
| 65 | + $popt->setEditSection(false);
|
| 66 | + $pout = $wgParser->parse($result . '__NOTOC__', $wgTitle, $popt);
|
| 67 | + /// NOTE: as of MW 1.14SVN, there is apparently no better way to hide the TOC
|
| 68 | + SMWOutputs::requireFromParserOutput($pout);
|
| 69 | + $result = $pout->getText();
|
| 70 | + }
|
| 71 | +
|
| 72 | + // add target="_new" for all links
|
| 73 | + $pattern = "|<a|i";
|
| 74 | + $result = preg_replace($pattern, '<a target="_new"', $result);
|
| 75 | + }
|
| 76 | + return $result;
|
| 77 | + }
|
| 78 | + else if($method == "updateStates"){
|
| 79 | + if ($smwgQEnabled) {
|
| 80 | + $result = SMWNotifyProcessor::updateStates($p_array);
|
| 81 | + }
|
| 82 | + return $result;
|
| 83 | + }
|
| 84 | + else if($method == "updateReportAll"){
|
| 85 | + if ($smwgQEnabled) {
|
| 86 | + $result = SMWNotifyProcessor::updateReportAll($p_array);
|
| 87 | + }
|
| 88 | + return $result;
|
| 89 | + }
|
| 90 | + else if($method == "updateShowAll"){
|
| 91 | + if ($smwgQEnabled) {
|
| 92 | + $result = SMWNotifyProcessor::updateShowAll($p_array);
|
| 93 | + }
|
| 94 | + return $result;
|
| 95 | + }
|
| 96 | + else if($method == "updateDelegates"){
|
| 97 | + if ($smwgQEnabled) {
|
| 98 | + $result = SMWNotifyProcessor::updateDelegates(explode("|", $params));
|
| 99 | + }
|
| 100 | + return $result;
|
| 101 | + }
|
| 102 | + else if($method == "delNotify"){
|
| 103 | + if ($smwgQEnabled) {
|
| 104 | + $result = SMWNotifyProcessor::delNotify($p_array);
|
| 105 | + }
|
| 106 | + return $result;
|
| 107 | + }
|
| 108 | + else {
|
| 109 | + return "Operation failed, please retry later.";
|
| 110 | + }
|
| 111 | +}
|
| 112 | +?> |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/specials/SMWNotifyMe/SMWNotifyMe.php |
— | — | @@ -0,0 +1,406 @@ |
| 2 | +<?php
|
| 3 | +
|
| 4 | +if (!defined('MEDIAWIKI')) die();
|
| 5 | +
|
| 6 | +
|
| 7 | +
|
| 8 | +global $IP;
|
| 9 | +require_once( $IP . "/includes/SpecialPage.php" );
|
| 10 | +require_once( "SMW_NotAjaxAccess.php" );
|
| 11 | +
|
| 12 | +global $smwgNMIP;
|
| 13 | +
|
| 14 | +require_once($smwgNMIP . '/includes/SMW_NotifyProcessor.php');
|
| 15 | +
|
| 16 | +/*
|
| 17 | + * Standard class that is resopnsible for the creation of the Special Page
|
| 18 | + */
|
| 19 | +class SMWNotifyMe extends SpecialPage {
|
| 20 | + public function __construct() {
|
| 21 | + parent::__construct('NotifyMe');
|
| 22 | + }
|
| 23 | +/*
|
| 24 | + * Overloaded function that is responsible for the creation of the Special Page
|
| 25 | + */
|
| 26 | + public function execute() {
|
| 27 | +
|
| 28 | + global $wgRequest, $wgOut, $smwgNMScriptPath, $wgUser;
|
| 29 | +
|
| 30 | + # Get query parameters
|
| 31 | + $feedFormat = $wgRequest->getVal( 'feed' );
|
| 32 | + if($feedFormat=='rss') {
|
| 33 | + # 10 seconds server-side caching max
|
| 34 | + $wgOut->setSquidMaxage( 10 );
|
| 35 | +
|
| 36 | + global $smwgNMMaxFeedItems;
|
| 37 | + $limit = ($smwgNMMaxFeedItems > 0) ? $smwgNMMaxFeedItems : 20;
|
| 38 | +
|
| 39 | + $dbr = wfGetDB( DB_SLAVE );
|
| 40 | + $id = $wgRequest->getVal( 'uid' );
|
| 41 | + $type = "uid";
|
| 42 | + if($id) {
|
| 43 | + # Get last modified date, for client caching
|
| 44 | + # Don't use this if we are using the patrol feature, patrol changes don't update the timestamp
|
| 45 | + $lastmod = $dbr->selectField( 'smw_nm_rss', 'MAX(timestamp)', array('user_id'=>$id), 'NotifyMeRSS' );
|
| 46 | + } else {
|
| 47 | + $type = "nid";
|
| 48 | + $id = $wgRequest->getVal( 'nid' );
|
| 49 | + $lastmod = $dbr->selectField( 'smw_nm_rss', 'MAX(timestamp)', array('notify_id'=>$id), 'NotifyMeRSS' );
|
| 50 | + }
|
| 51 | + if( $lastmod && $wgOut->checkLastModified( $lastmod ) ){
|
| 52 | + # Client cache fresh and headers sent, nothing more to do.
|
| 53 | + return;
|
| 54 | + }
|
| 55 | + $this->rcOutputFeed( $feedFormat, $type, $id, $limit, $lastmod, true );
|
| 56 | + } else {
|
| 57 | + $user_id = $wgUser->getId();
|
| 58 | +
|
| 59 | + $wgOut->setPageTitle(wfMsg('smw_notifyme'));
|
| 60 | +
|
| 61 | + if($user_id > 0) {
|
| 62 | + $isSysop = in_array('sysop', $wgUser->getEffectiveGroups());
|
| 63 | + if($isSysop) {
|
| 64 | + SMWNotifyMe::addAutocomplete();
|
| 65 | + }
|
| 66 | +
|
| 67 | + $imagepath = $smwgNMScriptPath . '/skins/images/';
|
| 68 | +
|
| 69 | + $html = '<div id="nmcontent">
|
| 70 | + <div id="shade" style="display:none"></div>
|
| 71 | + <div id="fullpreviewbox" style="display:none">
|
| 72 | + <div id="fullpreview"></div>
|
| 73 | + <span class="nmbutton" onclick="$(\'fullpreviewbox\', \'shade\').invoke(\'toggle\')"><img src="'. $imagepath. 'delete.png"/>Close Preview</span></div>
|
| 74 | + <div id="nmlayout">
|
| 75 | + <div id="querytitle" onclick="notifyhelper.switchquery()" onmouseover="Tip(\'' . wfMsg('smw_nm_tt_query') . '\')"><a id="querytitle-link" class="minusplus" href="javascript:void(0)"></a>Notify Me Query</div>
|
| 76 | + <div id="querycontent">'.$this->getQueryLayout().'</div>
|
| 77 | + <div id="layouttitle" onclick="notifyhelper.switchlayout()" onmouseover="Tip(\'' . wfMsg('smw_nm_tt_nmm') . '\')"><a id="layouttitle-link" class="minusplus" href="javascript:void(0)"></a>Notify Me Manager</div>
|
| 78 | + <div id="layoutcontent">' . $this->getNotifyTable().'</div>
|
| 79 | + </div>
|
| 80 | + </div>';
|
| 81 | +
|
| 82 | + global $wgEmailAuthentication, $wgEnableEmail;
|
| 83 | + if ($wgEnableEmail) {
|
| 84 | + if ($wgEmailAuthentication && ($wgUser->getEmail() != '') ) {
|
| 85 | + if( $wgUser->getEmailAuthenticationTimestamp() ) {
|
| 86 | + $disableEmailPrefs = false;
|
| 87 | + } else {
|
| 88 | + $disableEmailPrefs = true;
|
| 89 | + }
|
| 90 | + } else {
|
| 91 | + $disableEmailPrefs = false;
|
| 92 | + }
|
| 93 | + $eEmail = $wgUser->getOption( 'enotifyme' );
|
| 94 | + $html .= '<div class="nmmenubar">
|
| 95 | + <input id="nmemail" type="checkbox" value="1"'.($disableEmailPrefs?' disabled':'').($eEmail?' checked':'').'/> Enable \'Notify Me\' by E-mail ';
|
| 96 | + if($disableEmailPrefs) {
|
| 97 | + $href = htmlspecialchars( $wgUser->getSkin()->makeSpecialUrl( 'Preferences' ) );
|
| 98 | + $text = htmlspecialchars( wfMsg( 'mypreferences' ) );
|
| 99 | + $html .= " (Please enable your email account in '<a href=\"$href\">$text</a>'. )";
|
| 100 | + }
|
| 101 | + $html .= '</div>';
|
| 102 | + }
|
| 103 | +
|
| 104 | + $html .= '<div>
|
| 105 | + RSS Feed : <input id="nmrss" size="80" type="text" title="RSS feed url" value="'.$this->getTitle()->getFullURL( 'feed=rss&uid='.$user_id ).'" />
|
| 106 | + <button class="btn" onclick="notifyhelper.copyToClipboard(\'nmrss\')" onmouseover="this.className=\'btn btnhov\'; Tip(\'' . wfMsg('smw_nm_tt_clipboard') . '\')" onmouseout="this.className=\'btn\'">' . wfMsg('smw_qi_clipboard') . '</button>
|
| 107 | + </div>';
|
| 108 | +
|
| 109 | + $html .= '<script type="text/javascript" src="' . $smwgNMScriptPath . '/scripts/NotifyMe/nm_tooltip.js"></script>';
|
| 110 | + } else {
|
| 111 | + $html = '<div id="nmlayout">You have not logged in, please login first. Thanks.</div>';
|
| 112 | + }
|
| 113 | + $wgOut->addHTML($html);
|
| 114 | + }
|
| 115 | + }
|
| 116 | + private function getQueryLayout() {
|
| 117 | + global $wgUser;
|
| 118 | + $isSysop = in_array('sysop', $wgUser->getEffectiveGroups());
|
| 119 | +
|
| 120 | + $html = '<table style="width: 100%;"><tr><td width=40%>
|
| 121 | + <table width=100%>
|
| 122 | + <tr><th width=40%>Name :</th><td width=60% onmouseover="Tip(\'Name of the notification\')"><input type="text" id="nmqname"></td></tr>
|
| 123 | + <tr><th nowrap>Report all :</th><td onmouseover="Tip(\'Report all semantic semantic attribes\\\' change of monitored pages\')"><input type="checkbox" checked id="nmqrall"></td></tr>
|
| 124 | + <tr><th nowrap>Show all :</th><td onmouseover="Tip(\'Show all query results with notifications\')"><input type="checkbox" id="nmqsall"></td></tr>';
|
| 125 | + if($isSysop) {
|
| 126 | + $html .= '<tr><th nowrap>Delegate to :</th><td><input type="text" id="nmd_new" size=35>
|
| 127 | + <div class="page_name_auto_complete" id="nmdiv_new"></div></td>';
|
| 128 | + }
|
| 129 | + $html .= '</table></td><td width=60%><table style="width: 100%;">
|
| 130 | + <tr><th onmouseover="Tip(\'' . wfMsg('smw_nm_tt_qtext') . '\')">Query (table format, all link only)</th></tr>
|
| 131 | + <tr><td onmouseover="Tip(\'Full query string, with {{#ask syntax\')">
|
| 132 | + {{#ask:<br/>
|
| 133 | + <textarea id="nmquery" cols="20" rows="6"></textarea><br/>
|
| 134 | + | format=table<br/>
|
| 135 | + | link=all<br/>
|
| 136 | + |}}
|
| 137 | + </td></tr>
|
| 138 | + </table>
|
| 139 | + </td></tr></table>
|
| 140 | + <div class="nmmenubar">
|
| 141 | + <div style="text-align:left;float:left;">
|
| 142 | + <button class="btn" onclick="notifyhelper.previewQuery()" onmouseover="this.className=\'btn btnhov\'; Tip(\'Show full preview of your query results\')" onmouseout="this.className=\'btn\'">Preview Results</button>
|
| 143 | + <button class="btn" onclick="notifyhelper.doSaveToNotify('.$isSysop.')" onmouseover="this.className=\'btn btnhov\'; Tip(\'Add this notification to you NotifyMe\')" onmouseout="this.className=\'btn\'">Add to NotifyMe</button>
|
| 144 | + </div><div style="text-align:right;">
|
| 145 | + <button class="btn" onclick="notifyhelper.resetQuery()"onmouseout="this.className=\'btn\'" onmouseover="this.className=\'btn btnhov\'; Tip(\'Resets the entire query\')">Reset Query</button>
|
| 146 | + </div></div>';
|
| 147 | +
|
| 148 | + return $html;
|
| 149 | + }
|
| 150 | +
|
| 151 | + static function addAutocomplete() {
|
| 152 | + global $smwgNMDelegateQuery, $smwgNMDelegateUserGroup;
|
| 153 | + if(!$smwgNMDelegateQuery && !$smwgNMDelegateUserGroup) return;
|
| 154 | +
|
| 155 | + global $wgOut, $smwgNMScriptPath;
|
| 156 | + $nmScriptPath = $smwgNMScriptPath . '/specials/SMWNotifyMe';
|
| 157 | + $nmYUIBase = "http://yui.yahooapis.com/2.7.0/build/";
|
| 158 | +
|
| 159 | + $wgOut->addLink( array(
|
| 160 | + 'rel' => 'stylesheet',
|
| 161 | + 'type' => 'text/css',
|
| 162 | + 'media' => "screen, projection",
|
| 163 | + 'href' => $nmScriptPath . '/skins/NM_yui_autocompletion.css'
|
| 164 | + ));
|
| 165 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'yahoo/yahoo-min.js"></script>' . "\n");
|
| 166 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'dom/dom-min.js"></script>' . "\n");
|
| 167 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'event/event-min.js"></script>' . "\n");
|
| 168 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'get/get-min.js"></script>' . "\n");
|
| 169 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'connection/connection-min.js"></script>' . "\n");
|
| 170 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'json/json-min.js"></script>' . "\n");
|
| 171 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'datasource/datasource-min.js"></script>' . "\n");
|
| 172 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmYUIBase . 'autocomplete/autocomplete-min.js"></script>' . "\n");
|
| 173 | + $wgOut->addScript('<script type="text/javascript" src="' . $nmScriptPath . '/libs/NM_yui_autocompletion.js"></script>' . "\n");
|
| 174 | +
|
| 175 | + $pages = array();
|
| 176 | + if($smwgNMDelegateUserGroup) {
|
| 177 | + if($smwgNMDelegateUserGroup != "*" && !is_array($smwgNMDelegateUserGroup)) {
|
| 178 | + $groups = split(",", $smwgNMDelegateUserGroup);
|
| 179 | + for($i = count($groups)-1; $i>=0; --$i) {
|
| 180 | + $groups[$i] = strtolower(trim(str_replace("\'", "\\\'", $groups[$i])));
|
| 181 | + }
|
| 182 | + }
|
| 183 | + $pages = NMStorage::getDatabase()->getGroupedUsers($groups);
|
| 184 | + for($i = count($pages)-1; $i>=0; --$i) {
|
| 185 | + $pages[$i] = "['".str_replace("\'", "\\\'", $pages[$i])."']";
|
| 186 | + }
|
| 187 | + }
|
| 188 | + if($smwgNMDelegateQuery) {
|
| 189 | + global $smwgQDefaultNamespaces, $smwgQFeatures;
|
| 190 | + $qp = new SMWQueryParser($smwgQFeatures);
|
| 191 | + $qp->setDefaultNamespaces($smwgQDefaultNamespaces);
|
| 192 | + $desc = $qp->getQueryDescription($smwgNMDelegateQuery);
|
| 193 | +
|
| 194 | + $desc->prependPrintRequest(new SMWPrintRequest(SMWPrintRequest::PRINT_THIS, ""));
|
| 195 | +
|
| 196 | + $query = new SMWQuery($desc, true);
|
| 197 | +
|
| 198 | + $query_result = smwfGetStore()->getQueryResult($query);
|
| 199 | + while ($res = $query_result->getNext()) {
|
| 200 | + $pages[] = "['".str_replace("\'", "\\\'", $res[0]->getNextObject()->getWikiValue())."']";
|
| 201 | + }
|
| 202 | + }
|
| 203 | + global $smwgNMMaxAutocompleteValues;
|
| 204 | + if($smwgNMMaxAutocompleteValues <= 0) {
|
| 205 | + $smwgNMMaxAutocompleteValues = 10;
|
| 206 | + }
|
| 207 | + $wgOut->addScript('<script type="text/javascript">
|
| 208 | + nmautocompletestrings = ['.join(',', $pages).'];
|
| 209 | + nmMaxResultsDisplayed = '.$smwgNMMaxAutocompleteValues.';
|
| 210 | + </script>');
|
| 211 | + }
|
| 212 | + private function getNotifyTable() {
|
| 213 | + global $wgUser;
|
| 214 | + $isSysop = in_array('sysop', $wgUser->getEffectiveGroups());
|
| 215 | +
|
| 216 | + $cols = 6;
|
| 217 | + $html = '<table width="100%" class="smwtable" id="nmtable">
|
| 218 | + <tr><th width="5%" onmouseover="Tip(\'Select notifications to be deleted\')">Delete?</th>
|
| 219 | + <th width="20%" onmouseover="Tip(\'Name of the notification\')">Name</th>
|
| 220 | + <th width="40%" onmouseover="Tip(\'Full query string, with {{#ask syntax\')" nowrap>Query String</th>
|
| 221 | + <th width="5%" onmouseover="Tip(\'Report all semantic semantic attribes\\\' change of monitored pages\')" nowrap>Report All</th>
|
| 222 | + <th width="5%" onmouseover="Tip(\'Show all query results with notifications\')" nowrap>Show All</th>
|
| 223 | + <th width="5%" onmouseover="Tip(\'Current state of notification. Enabled?\')">Enabled?</th>';
|
| 224 | + if($isSysop) {
|
| 225 | + $cols ++;
|
| 226 | + $html .= '<th width="20%" onmouseover="Tip(\'Add delegate users to recieve NotifyMe, separated by comma\')">Delegate</th>';
|
| 227 | + }
|
| 228 | + $html .= '</tr>';
|
| 229 | + $notifications = SMWNotifyProcessor::getNotifications();
|
| 230 | + if ($notifications != null) {
|
| 231 | + foreach ($notifications as $row) {
|
| 232 | + $html .= '<tr>
|
| 233 | + <td><input type="checkbox" name="nmdel" value='.$row['notify_id'].'></td>
|
| 234 | + <td><a target="_blank" href="'.$this->getTitle()->getFullURL( 'feed=rss&nid='.$row['notify_id'] ).'">'.$row['name'].'</a></td>
|
| 235 | + <td>'.str_replace("\n", "<br/>", $row['query']).'</td>
|
| 236 | + <td><input type="checkbox" '.($row['rep_all']?'checked':'').' name="nmall" value='.$row['notify_id'].'></td>
|
| 237 | + <td><input type="checkbox" '.($row['show_all']?'checked':'').' name="nmsall" value='.$row['notify_id'].'></td>
|
| 238 | + <td><input type="checkbox" '.($row['enable']?'checked':'').' name="nmenable" id="nmenable_'.$row['notify_id'].'" value='.$row['notify_id'].'></td>';
|
| 239 | + if($isSysop) {
|
| 240 | + $html .= '<td><input type="text" name="nmdelegate" id=nmd_'.$row['notify_id'].' value="'.$row['delegate'].'">
|
| 241 | + <div class="page_name_auto_complete" id="nmdiv_'.$row['notify_id'].'"></div></td>';
|
| 242 | + }
|
| 243 | + $html .= '</tr>';
|
| 244 | + }
|
| 245 | + }
|
| 246 | + $html .= '<tr id="nmtoolbar">
|
| 247 | + <td><a href="#" onclick="notifyhelper.delall(true)">ALL</a>/<a href="#" onclick="notifyhelper.delall(false)">NONE</a> <button class="btn" onclick="notifyhelper.deleteNotify()" onmouseover="this.className=\'btn btnhov\'; Tip(\'Delete the checked notifications\')" onmouseout="this.className=\'btn\'">Update</button></td>
|
| 248 | + <td></td><td></td>
|
| 249 | + <td><a href="#" onclick="notifyhelper.reportall(true)">ALL</a>/<a href="#" onclick="notifyhelper.reportall(false)">NONE</a> <button class="btn" onclick="notifyhelper.updateReportAll()" onmouseover="this.className=\'btn btnhov\'; Tip(\'Report all semantic attributes\\\' change on checked notifications\')" onmouseout="this.className=\'btn\'">Update</button></td>
|
| 250 | + <td><a href="#" onclick="notifyhelper.showall(true)">ALL</a>/<a href="#" onclick="notifyhelper.showall(false)">NONE</a> <button class="btn" onclick="notifyhelper.updateShowAll()" onmouseover="this.className=\'btn btnhov\'; Tip(\'Show all query results with notifications\')" onmouseout="this.className=\'btn\'">Update</button></td>
|
| 251 | + <td><a href="#" onclick="notifyhelper.enableall(true)">ALL</a>/<a href="#" onclick="notifyhelper.enableall(false)">NONE</a> <button class="btn" onclick="notifyhelper.updateStates()" onmouseover="this.className=\'btn btnhov\'; Tip(\'Update the states of your notifications, enable or disable them\')" onmouseout="this.className=\'btn\'">Update</button></td>';
|
| 252 | + if($isSysop)
|
| 253 | + $html .= '<td><button class="btn" onclick="notifyhelper.updateDelegate()" onmouseover="this.className=\'btn btnhov\'; Tip(\'Update delegate\')" onmouseout="this.className=\'btn\'">Update</button></td>';
|
| 254 | + $html .= '</tr></table>';
|
| 255 | + return $html;
|
| 256 | + }
|
| 257 | +
|
| 258 | + // rss feed related features
|
| 259 | + function rcOutputFeed( $feedFormat, $type, $id, $limit, $lastmod, $queryresult_item = false) {
|
| 260 | + global $messageMemc, $wgFeedCacheTimeout;
|
| 261 | + global $wgFeedClasses, $wgTitle, $wgSitename, $wgContLanguageCode;
|
| 262 | +
|
| 263 | + if( !isset( $wgFeedClasses[$feedFormat] ) ) {
|
| 264 | + wfHttpError( 500, "Internal Server Error", "Unsupported feed type." );
|
| 265 | + return false;
|
| 266 | + }
|
| 267 | +
|
| 268 | + $timekey = wfMemcKey( 'nmfeed', $feedFormat, $type, $id, 'timestamp' );
|
| 269 | + $key = wfMemcKey( 'nmfeed', $feedFormat, $type, $id, 'limit', $limit );
|
| 270 | +
|
| 271 | + //purge cache if requested
|
| 272 | + global $wgRequest, $wgUser;
|
| 273 | + $purge = $wgRequest->getVal( 'action' ) == 'purge';
|
| 274 | + if ( $purge && $wgUser->isAllowed('purge') ) {
|
| 275 | + $messageMemc->delete( $timekey );
|
| 276 | + $messageMemc->delete( $key );
|
| 277 | + }
|
| 278 | +
|
| 279 | + /**
|
| 280 | + * Bumping around loading up diffs can be pretty slow, so where
|
| 281 | + * possible we want to cache the feed output so the next visitor
|
| 282 | + * gets it quick too.
|
| 283 | + */
|
| 284 | + $cachedFeed = false;
|
| 285 | + if( ( $wgFeedCacheTimeout > 0 ) && ( $feedLastmod = $messageMemc->get( $timekey ) ) ) {
|
| 286 | + /**
|
| 287 | + * If the cached feed was rendered very recently, we may
|
| 288 | + * go ahead and use it even if there have been edits made
|
| 289 | + * since it was rendered. This keeps a swarm of requests
|
| 290 | + * from being too bad on a super-frequently edited wiki.
|
| 291 | + */
|
| 292 | + if( time() - wfTimestamp( TS_UNIX, $feedLastmod ) < $wgFeedCacheTimeout ||
|
| 293 | + wfTimestamp( TS_UNIX, $feedLastmod ) > wfTimestamp( TS_UNIX, $lastmod ) ) {
|
| 294 | + wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" );
|
| 295 | + $cachedFeed = $messageMemc->get( $key );
|
| 296 | + } else {
|
| 297 | + wfDebug( "RC: cached feed timestamp check failed ($feedLastmod; $lastmod)\n" );
|
| 298 | + }
|
| 299 | + }
|
| 300 | +
|
| 301 | + $feedTitle = $wgSitename . ' - Recent changes to ';
|
| 302 | + if($type = "nid") {
|
| 303 | + $dbr = wfGetDB( DB_SLAVE );
|
| 304 | + $notify_name = $dbr->selectField( 'smw_nm_query', 'name', array('notify_id'=>$id), 'NotifyMeRSS' );
|
| 305 | + $feedTitle .= "\"$notify_name\"";
|
| 306 | + } else {
|
| 307 | + $feedTitle .= "Notify Me";
|
| 308 | + }
|
| 309 | + $feedTitle .= ' [' . $wgContLanguageCode . ']';
|
| 310 | +
|
| 311 | + $feed = new $wgFeedClasses[$feedFormat](
|
| 312 | + $feedTitle,
|
| 313 | + 'Track the most recent changes to '.($notify_name?"\"$notify_name\"":'Notify Me').' in this feed.',
|
| 314 | + $wgTitle->getFullUrl() );
|
| 315 | +
|
| 316 | + if( is_string( $cachedFeed ) ) {
|
| 317 | + wfDebug( "RC: Outputting cached feed\n" );
|
| 318 | + $feed->httpHeaders();
|
| 319 | + echo $cachedFeed;
|
| 320 | + } else {
|
| 321 | + wfDebug( "RC: rendering new feed and caching it\n" );
|
| 322 | + ob_start();
|
| 323 | + $this->rcDoOutputFeed( $feed, $queryresult_item, $type, $id, $limit );
|
| 324 | + $cachedFeed = ob_get_contents();
|
| 325 | + ob_end_flush();
|
| 326 | +
|
| 327 | + $expire = 3600 * 24; # One day
|
| 328 | + $messageMemc->set( $key, $cachedFeed );
|
| 329 | + $messageMemc->set( $timekey, wfTimestamp( TS_MW ), $expire );
|
| 330 | + }
|
| 331 | + return true;
|
| 332 | + }
|
| 333 | +
|
| 334 | + function rcDoOutputFeed( &$feed, $queryresult_item, $type, $id, $limit ) {
|
| 335 | + wfProfileIn( __METHOD__ );
|
| 336 | +
|
| 337 | + $feed->outHeader();
|
| 338 | +
|
| 339 | + if($queryresult_item && $type=="nid") {
|
| 340 | + $dbr = wfGetDB( DB_SLAVE );
|
| 341 | + $showall = $dbr->selectField( 'smw_nm_query', 'show_all', array('notify_id'=>$id), 'NotifyMeRSS' );
|
| 342 | + if($showall) {
|
| 343 | + $query = $dbr->selectField( 'smw_nm_query', 'query', array('notify_id'=>$id), 'NotifyMeRSS' );
|
| 344 | + SMWQueryProcessor::processFunctionParams(explode("\n", $query), $querystring, $params, $printouts);
|
| 345 | + $query = SMWQueryProcessor::createQuery($querystring, $params, SMWQueryProcessor::INLINE_QUERY, 'auto', $printouts);
|
| 346 | + $res = smwfGetStore()->getQueryResult($query);
|
| 347 | +
|
| 348 | + $items = array();
|
| 349 | + $labels = array();
|
| 350 | + foreach ($res->getPrintRequests() as $pr) {
|
| 351 | + $labels[] = $pr->getText(SMW_OUTPUT_WIKI);
|
| 352 | + }
|
| 353 | + $row = $res->getNext();
|
| 354 | + $linker = new Linker();
|
| 355 | + while ( $row !== false ) {
|
| 356 | + $wikipage = $row[0]->getNextObject(); // get the object
|
| 357 | + $a = new Article($wikipage->getTitle());
|
| 358 | + $description = "<table style=\"width: 60em; font-size: 90%; border: 1px solid #aaaaaa; background-color: #f9f9f9; color: black; margin-bottom: 0.5em; margin-left: 1em; padding: 0.2em; clear: right; text-align:left;\"><tr><th style=\"text-align: center; background-color:#ccccff;\" colspan=\"2\"><big>".$wikipage->getText()."</big></th></tr>";
|
| 359 | + $idx = 0;
|
| 360 | + foreach ($row as $field) {
|
| 361 | + $description .= "<tr><td>".$labels[$idx]."</td><td>";
|
| 362 | + $first_value = true;
|
| 363 | + while ( ($object = $field->getNextObject()) !== false ) {
|
| 364 | + if ($first_value) $first_value = false; else $description .= ', ';
|
| 365 | + $description .= $object->getShortText(SMW_OUTPUT_HTML, $linker);
|
| 366 | + }
|
| 367 | + $description .= "</td></tr>";
|
| 368 | + $idx ++;
|
| 369 | + }
|
| 370 | + $description .= "</table>";
|
| 371 | + $items[] = array ('title'=>$wikipage->getText(),'notify'=>$description,'timestamp'=>$a->getTimestamp());
|
| 372 | + $row = $res->getNext();
|
| 373 | + }
|
| 374 | + } else {
|
| 375 | + $items = NMStorage::getDatabase()->getNotifyRSS($type, $id, $limit);
|
| 376 | + }
|
| 377 | + } else {
|
| 378 | + $items = NMStorage::getDatabase()->getNotifyRSS($type, $id, $limit);
|
| 379 | + }
|
| 380 | + foreach( $items as $i ) {
|
| 381 | + if(isset($i['link']) && $i['link']) {
|
| 382 | + $item = new FeedItem(
|
| 383 | + $i['title'],
|
| 384 | + $i['notify'],
|
| 385 | + $i['link'],
|
| 386 | + $i['timestamp']
|
| 387 | + );
|
| 388 | + } else {
|
| 389 | + $title = Title::makeTitle( NS_MAIN, $i['title'] );
|
| 390 | + $talkpage = $title->getTalkPage();
|
| 391 | + $item = new FeedItem(
|
| 392 | + $title->getPrefixedText(),
|
| 393 | + $i['notify'],
|
| 394 | + $title->getFullURL(),
|
| 395 | + $i['timestamp'],
|
| 396 | + "",
|
| 397 | + $talkpage->getFullURL()
|
| 398 | + );
|
| 399 | + }
|
| 400 | + $feed->outItem( $item );
|
| 401 | + }
|
| 402 | +
|
| 403 | + $feed->outFooter();
|
| 404 | + wfProfileOut( __METHOD__ );
|
| 405 | + }
|
| 406 | +}
|
| 407 | +?> |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/specials/SMWNotifyMe/skins/NM_yui_autocompletion.css |
— | — | @@ -0,0 +1,51 @@ |
| 2 | +/* Custom styles for Semantic Forms autocompletion with Yahoo! UI (YUI) */ |
| 3 | +/* Most of these calls are meant to get the look to match YUI's "Sam" |
| 4 | + skin, for those wikis that don't have a "yui-skin-sam" declaration |
| 5 | + anywhere in their skin. */ |
| 6 | + |
| 7 | +.yui-ac-content ul { |
| 8 | + margin: 0pt; |
| 9 | + padding: 0pt; |
| 10 | + width: 100%; |
| 11 | +} |
| 12 | +.yui-ac-content li { |
| 13 | + cursor: default; |
| 14 | + margin: 0pt; |
| 15 | + padding: 2px 40px 2px 5px; |
| 16 | + white-space: nowrap; |
| 17 | +} |
| 18 | +.yui-skin-sam .yui-ac-content li.yui-ac-highlight { |
| 19 | + color: white; |
| 20 | + background-color: #222288; |
| 21 | +} |
| 22 | +.yui-ac-content li.yui-ac-highlight { |
| 23 | + color: white; |
| 24 | + background-color: #222288; |
| 25 | +} |
| 26 | +.yui-ac-content { |
| 27 | + background: #FFFFFF none repeat scroll 0%; |
| 28 | + border: 1px solid #808080; |
| 29 | + overflow:hidden; |
| 30 | + position: absolute; |
| 31 | + z-index: 9050; |
| 32 | +} |
| 33 | +.yui-ac-bd ul { |
| 34 | + margin-left: 0; |
| 35 | + list-style-image: none; |
| 36 | + list-style-type: none; |
| 37 | +} |
| 38 | +/* Override unneeded settings from YUI's Sam skin */ |
| 39 | +.yui-skin-sam .yui-ac-content { |
| 40 | + width: auto; |
| 41 | +} |
| 42 | +.yui-skin-sam .yui-ac-content li { |
| 43 | + padding-right: 40px; |
| 44 | +} |
| 45 | +.yui-skin-sam .yui-ac-input { |
| 46 | + width: auto; |
| 47 | + position: static; |
| 48 | +} |
| 49 | +.yui-skin-sam .yui-ac-container { |
| 50 | + width: auto; |
| 51 | + position: static; |
| 52 | +} |
Index: trunk/extensions/SemanticNotifyMe/specials/SMWNotifyMe/libs/NM_yui_autocompletion.js |
— | — | @@ -0,0 +1,152 @@ |
| 2 | +/*
|
| 3 | + * nm_autocomplete()
|
| 4 | + *
|
| 5 | + * Creates an autocompletion widget, using the Yahoo! UI (YUI) library,
|
| 6 | + * for the specified form input, using the specified values and delimiter
|
| 7 | + * (in the case that it's a multiple-values autocompletion)
|
| 8 | + */
|
| 9 | +
|
| 10 | +function nm_autocomplete(input_name, container_name, values, delimiter) {
|
| 11 | + if (values != null) {
|
| 12 | + this.oACDS = new YAHOO.widget.DS_JSFunction(nmAutocompleteFunctionGenerator(values));
|
| 13 | + this.oACDS.maxCacheEntries = 0;
|
| 14 | + this.oAutoComp = new YAHOO.widget.AutoComplete(input_name, container_name, this.oACDS);
|
| 15 | + }
|
| 16 | +
|
| 17 | + // Instantiate AutoComplete
|
| 18 | + this.oAutoComp.alwaysShowContainer = false;
|
| 19 | + //this.oAutoComp.minQueryLength = 1;
|
| 20 | + this.oAutoComp.maxResultsDisplayed = nmMaxResultsDisplayed;
|
| 21 | + this.oAutoComp.animHoriz = false;
|
| 22 | + this.oAutoComp.animVert = false;
|
| 23 | + if (delimiter != '') {
|
| 24 | + this.oAutoComp.delimChar = delimiter;
|
| 25 | + }
|
| 26 | + this.oAutoComp.minQueryLength = 0;
|
| 27 | + this.oAutoComp.textboxFocusEvent.subscribe(
|
| 28 | + function (sType, aArgs) {
|
| 29 | + var oAutoComp = aArgs[0];
|
| 30 | + oAutoComp.sendQuery("");
|
| 31 | + });
|
| 32 | +
|
| 33 | + // don't set IFrame, which is meant to improve formatting on Internet
|
| 34 | + // Explorer - currently, it only messes up the formatting
|
| 35 | + //this.oAutoComp.useIFrame = true;
|
| 36 | + // This function returns markup that bolds the original query
|
| 37 | + this.oAutoComp.formatResult = function(aResultItem, sQuery) {
|
| 38 | + var sKey = aResultItem[0]; // the entire result key
|
| 39 | + var sKeyIndex = sKey.toLowerCase().indexOf(sQuery.toLowerCase());
|
| 40 | + // if it's not the very beginning, make sure the query string
|
| 41 | + // comes after a space
|
| 42 | + if (sKeyIndex > 0) {
|
| 43 | + sKeyIndex = sKey.toLowerCase().indexOf(' ' + sQuery.toLowerCase()) + 1;
|
| 44 | + }
|
| 45 | + var sKeyBefore = sKey;
|
| 46 | + var sKeyQuery = "";
|
| 47 | + var sKeyAfter = "";
|
| 48 | + if (sKeyIndex >= 0) {
|
| 49 | + sKeyBefore = sKey.substr(0, sKeyIndex);
|
| 50 | + sKeyQuery = sKey.substr(sKeyIndex, sQuery.length); // the query itself
|
| 51 | + sKeyAfter = sKey.substr(sKeyIndex + sQuery.length); // the rest of the result
|
| 52 | + }
|
| 53 | +
|
| 54 | + var aMarkup = sKeyBefore +
|
| 55 | + "<span style='font-weight:bold'>" +
|
| 56 | + sKeyQuery +
|
| 57 | + "</span>" +
|
| 58 | + sKeyAfter;
|
| 59 | + return aMarkup;
|
| 60 | + };
|
| 61 | +
|
| 62 | + // Show custom message if no results found
|
| 63 | + this.myOnDataReturn = function(sType, aArgs) {
|
| 64 | + var oAutoComp = aArgs[0];
|
| 65 | + var sQuery = aArgs[1];
|
| 66 | + var aResults = aArgs[2];
|
| 67 | +
|
| 68 | + if(aResults.length == 0) {
|
| 69 | + oAutoComp.setBody("<div>No matching results</div>");
|
| 70 | + }
|
| 71 | + };
|
| 72 | +
|
| 73 | + var itemSelectHandler = function (oAutoComp, listItem, dataArray) {
|
| 74 | + }
|
| 75 | +
|
| 76 | + // neither of these events are necessary to subscribe to yet
|
| 77 | + //this.oAutoComp.itemSelectEvent.subscribe(itemSelectHandler);
|
| 78 | + //this.oAutoComp.dataReturnEvent.subscribe(this.myOnDataReturn);
|
| 79 | +
|
| 80 | + // Preload content in the container
|
| 81 | + //this.oAutoComp.sendQuery("");
|
| 82 | +};
|
| 83 | +
|
| 84 | +/*
|
| 85 | + * Helper function - returns a function that returns, for a given query
|
| 86 | + * string, an array of values that match it
|
| 87 | + */
|
| 88 | +function nmAutocompleteFunctionGenerator(values_list) {
|
| 89 | + return function (sQuery) {
|
| 90 | + var primaryResults = [];
|
| 91 | + var secondaryResults = [];
|
| 92 | + if (sQuery && sQuery.length > 0) {
|
| 93 | + // in some cases, decodeURI() doesn't handle colons correctly -
|
| 94 | + // replace them manually
|
| 95 | + query_str = decodeURI(sQuery).replace(/(%3A)/g, ":").toLowerCase();
|
| 96 | + for (var i = 0; i < values_list.length; i++) {
|
| 97 | + subarray = values_list[i];
|
| 98 | + // workaround for strange IE bug
|
| 99 | + var name;
|
| 100 | + if (subarray.length > 1) {
|
| 101 | + name = subarray;
|
| 102 | + } else {
|
| 103 | + name = subarray[0];
|
| 104 | + }
|
| 105 | + name_str = name.toLowerCase();
|
| 106 | + var sKeyIndex = name_str.indexOf(query_str);
|
| 107 | + if (sKeyIndex == 0) {
|
| 108 | + primaryResults.push(subarray);
|
| 109 | + } else {
|
| 110 | + var index2 = name_str.indexOf(" " + query_str);
|
| 111 | + if (index2 >= 0) {
|
| 112 | + secondaryResults.push(subarray);
|
| 113 | + }
|
| 114 | + }
|
| 115 | + }
|
| 116 | + var aResults = primaryResults.concat(secondaryResults);
|
| 117 | + // return no values if there's only one value, and the query
|
| 118 | + // exactly matches it
|
| 119 | + if (aResults.length == 1 && query_str.length == aResults[0][0].length) {
|
| 120 | + return [];
|
| 121 | + } else {
|
| 122 | + return aResults;
|
| 123 | + }
|
| 124 | + }
|
| 125 | + // Empty queries return all values
|
| 126 | + else {
|
| 127 | + return values_list;
|
| 128 | + }
|
| 129 | + }
|
| 130 | +}
|
| 131 | +
|
| 132 | +function nmAttachAutocompleteToAllFields()
|
| 133 | +{
|
| 134 | + var inputs = document.getElementsByName("nmdelegate");
|
| 135 | + var y;
|
| 136 | + for (y = 0; y < inputs.length; y++) {
|
| 137 | + nmAttachAutocompleteToField(inputs[y].id);
|
| 138 | + }
|
| 139 | + nmAttachAutocompleteToField("nmd_new");
|
| 140 | +}
|
| 141 | +
|
| 142 | +var nmautocompletestrings = new Array();
|
| 143 | +var nmMaxResultsDisplayed = 10;
|
| 144 | +
|
| 145 | +//Activate autocomplete functionality for the specified field
|
| 146 | +function nmAttachAutocompleteToField(input_id)
|
| 147 | +{
|
| 148 | + var div_id = input_id.replace(/nmd_/g, 'nmdiv_');
|
| 149 | + var delimiter = ",";
|
| 150 | + nm_autocomplete(input_id, div_id, nmautocompletestrings, delimiter);
|
| 151 | +}
|
| 152 | +
|
| 153 | +YAHOO.util.Event.addListener(window, 'load', nmAttachAutocompleteToAllFields);
|
Index: trunk/extensions/SemanticNotifyMe/INSTALL |
— | — | @@ -0,0 +1,18 @@ |
| 2 | +== Requirements ==
|
| 3 | +
|
| 4 | +* MediaWiki 1.13.5
|
| 5 | +* Semantic MediaWiki 1.4.2
|
| 6 | +* PHP 5.x or greater installed and working
|
| 7 | +* MySQL >= 4.0.14 (version required by MediaWiki)
|
| 8 | +* Halo extension 1.4.4 (optional)
|
| 9 | +
|
| 10 | +== Installation ==
|
| 11 | +
|
| 12 | +Edit $IP/LocalSettings.php and add:
|
| 13 | +
|
| 14 | + include_once('extensions/SemanticNotifyMe/includes/SNM_Initialize.php');
|
| 15 | +
|
| 16 | +Apply this patch, $IP/extensions/SemanticMediaWiki/includes/storage/SMW_SQLStore2.php
|
| 17 | +line 1268, add the following
|
| 18 | + $title = Title::makeTitle(NS_SPECIAL, 'SMWNotifyMe');
|
| 19 | + $updatejobs[] = new SMWNMRefreshJob($title);
|
Index: trunk/extensions/SemanticNotifyMe/RELEASE-NOTES |
— | — | @@ -0,0 +1,9 @@ |
| 2 | +For a documentation of all features, see http://www.mediawiki.org/wiki/Extension:Semantic_NotifyMe
|
| 3 | +
|
| 4 | +== Semantic NotifyMe 0.5 ==
|
| 5 | +
|
| 6 | +This is a pre-alpha version of the Semantic NotifyMe extensions.
|
| 7 | +It includes:
|
| 8 | +
|
| 9 | +* support for realtime notification on Semantic property/value changing
|
| 10 | +* Special:NotifyMe, manage all NotifyMe notifications, delegation enabled
|
Index: trunk/extensions/SemanticNotifyMe/includes/SMW_NotifyProcessor.php |
— | — | @@ -0,0 +1,1655 @@ |
| 2 | +<?php
|
| 3 | +/**
|
| 4 | + * This file contains a static class for accessing functions to generate and execute
|
| 5 | + * notify me semantic queries and to serialise their results.
|
| 6 | + *
|
| 7 | + * @author dch
|
| 8 | + */
|
| 9 | +
|
| 10 | +global $smwgIP, $smwgNMIP ;
|
| 11 | +require_once($smwgIP . '/includes/storage/SMW_Store.php');
|
| 12 | +require_once( $smwgNMIP . '/includes/SMW_NMStorage.php' );
|
| 13 | +
|
| 14 | +/**
|
| 15 | + * Static class for accessing functions to generate and execute semantic queries
|
| 16 | + * and to serialise their results.
|
| 17 | + */
|
| 18 | +class SMWNotifyProcessor {
|
| 19 | +
|
| 20 | + static public function getNotifications() {
|
| 21 | + $sStore = NMStorage::getDatabase();
|
| 22 | + global $wgUser;
|
| 23 | + $user_id = $wgUser->getId();
|
| 24 | +
|
| 25 | + $notifications = $sStore->getNotifications($user_id);
|
| 26 | +
|
| 27 | + return $notifications;
|
| 28 | + }
|
| 29 | +
|
| 30 | + /**
|
| 31 | + * Enable a NotifyMe with specified id and querystring
|
| 32 | + *
|
| 33 | + * used for inline query only
|
| 34 | + */
|
| 35 | + static public function enableNotify($notify_id, $querystring, &$msg = NULL) {
|
| 36 | + wfProfileIn('SMWNotifyProcessor::enableNotify (SMW)');
|
| 37 | +
|
| 38 | + $sStore = NMStorage::getDatabase();
|
| 39 | + global $smwgQDefaultNamespaces;
|
| 40 | +
|
| 41 | + SMWQueryProcessor::processFunctionParams(SMWNotifyProcessor::getQueryRawParams($querystring),$querystring,$params,$printouts);
|
| 42 | + $relatedArticles = array();
|
| 43 | + foreach($printouts as $po) {
|
| 44 | + if($po == $params['sort']) $sorted = true;
|
| 45 | + $printoutArticles[] = array(
|
| 46 | + 'namespace' => SMW_NS_PROPERTY,
|
| 47 | + 'title' => Title::makeTitle( SMW_NS_PROPERTY, $po->getText() )->getDBkey());
|
| 48 | + }
|
| 49 | + if(!$sorted && isset($params['sort'])) {
|
| 50 | + $printoutArticles[] = array(
|
| 51 | + 'namespace' => SMW_NS_PROPERTY,
|
| 52 | + 'title' => Title::makeTitle( SMW_NS_PROPERTY, $params['sort'] )->getDBkey());
|
| 53 | + }
|
| 54 | +
|
| 55 | + $qp = new SMWNotifyParser($notify_id, $printoutArticles);
|
| 56 | + $qp->setDefaultNamespaces($smwgQDefaultNamespaces);
|
| 57 | + $desc = $qp->getQueryDescription($querystring);
|
| 58 | +
|
| 59 | + if(!$qp->m_result) {
|
| 60 | + $qp->m_errors[] = "The category / instance / property page in query may not exists.";
|
| 61 | + }
|
| 62 | +
|
| 63 | + if(isset($msg) && $qp->hasSubquery()) {
|
| 64 | + $msg .= "\nThe query contains subquery, which may affect the precision of notifications.";
|
| 65 | + }
|
| 66 | +
|
| 67 | + $query = new SMWQuery($desc, true, false);
|
| 68 | + $query->setQueryString($querystring);
|
| 69 | + $query->addErrors($qp->getErrors()); // keep parsing errors for later output
|
| 70 | +
|
| 71 | + $res = $sStore->getNMQueryResult($query);
|
| 72 | +
|
| 73 | + if(count($query->getErrors())>0) {
|
| 74 | + if(isset($msg)) {
|
| 75 | + $msg .= "\n\n" . implode('\n', $query->getErrors()) . "\n\nYou can enable the query in NotifyMe manager later.";
|
| 76 | + }
|
| 77 | + $sStore->disableNotifyState($notify_id);
|
| 78 | + wfProfileOut('SMWNotifyProcessor::enableNotify (SMW)');
|
| 79 | + return false;
|
| 80 | + }
|
| 81 | +
|
| 82 | + $sStore->updateNMSql($notify_id, $res['sql'], $res['tmp_hierarchy']);
|
| 83 | + if (count($res['page_ids']) > 0) {
|
| 84 | + $add_monitor = array();
|
| 85 | + foreach($res['page_ids'] as $page_id) {
|
| 86 | + $add_monitor[] = array('notify_id'=>$notify_id, 'page_id'=>$page_id);
|
| 87 | + }
|
| 88 | + $sStore->addNotifyMonitor($add_monitor);
|
| 89 | + }
|
| 90 | + $sStore->updateNotifyState($notify_id, 1);
|
| 91 | + wfProfileOut('SMWNotifyProcessor::enableNotify (SMW)');
|
| 92 | +
|
| 93 | + return true;
|
| 94 | + }
|
| 95 | +
|
| 96 | + static public function getQueryRawParams($querystring){
|
| 97 | + // read query with printouts and (possibly) other parameters like sort, order, limit, etc...
|
| 98 | + $pos = strpos($querystring, "|?");
|
| 99 | + if ($pos > 0) {
|
| 100 | + $rawparams[] = trim(substr($querystring, 0, $pos));
|
| 101 | + $ps = explode("|?", trim(substr($querystring, $pos+2)));
|
| 102 | + foreach ($ps as $param) {
|
| 103 | + $rawparams[] = "?" . trim($param);
|
| 104 | + }
|
| 105 | + } else {
|
| 106 | + $ps = preg_split('/[^\|]{1}\|{1}(?!\|)/s', $querystring);
|
| 107 | + if (count($ps) > 1) {
|
| 108 | + // last char of query condition is missing (matched with [^\|]{1}) therefore copy from original
|
| 109 | + $rawparams[] = trim(substr($querystring, 0, strlen($ps[0]) + 1));
|
| 110 | + array_shift($ps); // remove the query condition
|
| 111 | + // add other params for formating etc.
|
| 112 | + foreach ($ps as $param)
|
| 113 | + $rawparams[] = trim($param);
|
| 114 | + } // no single pipe found, no params specified in query
|
| 115 | + else $rawparams[] = trim($querystring);
|
| 116 | + }
|
| 117 | + $rawparams[] = "format=table";
|
| 118 | + $rawparams[] = "link=all";
|
| 119 | + return $rawparams;
|
| 120 | + }
|
| 121 | +
|
| 122 | + static public function addNotify($rawquery, $name, $rep_all, $show_all, $delegate) {
|
| 123 | + global $wgTitle;
|
| 124 | + // Take care at least of some templates -- for better template support use #ask
|
| 125 | + $parser = new Parser();
|
| 126 | + $parserOptions = new ParserOptions();
|
| 127 | + $parser->startExternalParse( $wgTitle, $parserOptions, OT_HTML );
|
| 128 | + $rawquery = $parser->transformMsg( $rawquery, $parserOptions );
|
| 129 | +
|
| 130 | + wfProfileIn('SMWNotifyProcessor::createNotify (SMW)');
|
| 131 | + $sStore = NMStorage::getDatabase();
|
| 132 | + global $wgUser;
|
| 133 | + $user_id = $wgUser->getId();
|
| 134 | + if($user_id == 0) {
|
| 135 | + wfProfileOut('SMWNotifyProcessor::createNotify (SMW)');
|
| 136 | + return "You have not logged in yet, please log in and retry again. Thanks.";
|
| 137 | + }
|
| 138 | +
|
| 139 | + // check notify query first, use QueryParser from SMW
|
| 140 | + SMWQueryProcessor::processFunctionParams(SMWNotifyProcessor::getQueryRawParams($rawquery),$querystring,$params,$printouts);
|
| 141 | +
|
| 142 | + $qp = new SMWQueryParser();
|
| 143 | + $qp->setDefaultNamespaces($smwgQDefaultNamespaces);
|
| 144 | + $desc = $qp->getQueryDescription($querystring);
|
| 145 | +
|
| 146 | + if(count($qp->getErrors())>0) {
|
| 147 | + wfProfileOut('SMWNotifyProcessor::createNotify (SMW)');
|
| 148 | + return "Notify create failed!\n\n" . implode('\n', $qp->getErrors()) . "\n\nPlease check the query and retry again";
|
| 149 | + }
|
| 150 | +
|
| 151 | + $notify_id = $sStore->addNotifyQuery($user_id, $rawquery, $name, $rep_all, $show_all, $delegate);
|
| 152 | + if($notify_id == 0) {
|
| 153 | + wfProfileOut('SMWNotifyProcessor::createNotify (SMW)');
|
| 154 | + return "Fail to save query. Please retry later...";
|
| 155 | + }
|
| 156 | + wfProfileOut('SMWNotifyProcessor::createNotify (SMW)');
|
| 157 | +
|
| 158 | + wfProfileIn('SMWNotifyProcessor::enableNotify (SMW)');
|
| 159 | + $msg = '';
|
| 160 | + $result = SMWNotifyProcessor::enableNotify($notify_id, $rawquery, $msg);
|
| 161 | + wfProfileOut('SMWNotifyProcessor::enableNotify (SMW)');
|
| 162 | + return "1".($result?"1":"0")."$notify_id,$msg";
|
| 163 | + }
|
| 164 | +
|
| 165 | + static public function updateStates($notify_ids) {
|
| 166 | + wfProfileIn('SMWNotifyProcessor::updateStates (SMW)');
|
| 167 | +
|
| 168 | + $notifications = SMWNotifyProcessor::getNotifications();
|
| 169 | + if ($notifications == null || !is_array($notifications)) {
|
| 170 | + return "No notifications available.";
|
| 171 | + }
|
| 172 | + $result = true;
|
| 173 | + $idx = 0;
|
| 174 | + $count = count($notify_ids) - 1;
|
| 175 | + $msg = '';
|
| 176 | + $errs = '';
|
| 177 | + $sStore = NMStorage::getDatabase();
|
| 178 | + foreach ($notifications as $row) {
|
| 179 | + if(($idx<$count) && ($notify_ids[$idx]==$row['notify_id'])) {
|
| 180 | + if($row['enable']==0) {
|
| 181 | + $m = '';
|
| 182 | + $r = SMWNotifyProcessor::enableNotify($row['notify_id'], $row['query'], $m);
|
| 183 | + if(!$r) {
|
| 184 | + $msg .= "NotifyMe : '" . $row['name'] . "'$m\n\n";
|
| 185 | + $errs .= $row['notify_id'] . ",";
|
| 186 | + $result = false;
|
| 187 | + }
|
| 188 | + }
|
| 189 | + $idx ++;
|
| 190 | + } else {
|
| 191 | + if($row['enable']==1) {
|
| 192 | + $result = $sStore->disableNotifyState($row['notify_id']);
|
| 193 | + }
|
| 194 | + }
|
| 195 | + }
|
| 196 | +
|
| 197 | + wfProfileOut('SMWNotifyProcessor::updateStates (SMW)');
|
| 198 | + return $result ? "States updated successfully!" : "0$errs|\n\n$msg";
|
| 199 | + }
|
| 200 | +
|
| 201 | + static public function updateDelegates($delegates) {
|
| 202 | + wfProfileIn('SMWNotifyProcessor::updateDelegates (SMW)');
|
| 203 | +
|
| 204 | + $notifications = SMWNotifyProcessor::getNotifications();
|
| 205 | + if ($notifications == null || !is_array($notifications)) {
|
| 206 | + return "No notifications available.";
|
| 207 | + }
|
| 208 | + $result = true;
|
| 209 | + $idx = 0;
|
| 210 | + $s = explode(':', $delegates[$idx], 2);
|
| 211 | + $count = count($delegates) - 1;
|
| 212 | + $sStore = NMStorage::getDatabase();
|
| 213 | + foreach ($notifications as $row) {
|
| 214 | + if(($idx<$count) && ($s[0]==$row['notify_id'])) {
|
| 215 | + $result = $sStore->updateDelegate($row['notify_id'], $s[1]);
|
| 216 | + $idx ++;
|
| 217 | + $s = explode(':', $delegates[$idx], 2);
|
| 218 | + } else {
|
| 219 | + $result = $sStore->updateDelegate($row['notify_id'], '');
|
| 220 | + }
|
| 221 | + if(!$result) break;
|
| 222 | + }
|
| 223 | +
|
| 224 | + wfProfileOut('SMWNotifyProcessor::updateDelegates (SMW)');
|
| 225 | + return $result ? "Delegates updated successfully!" : "Delegates updated error!";
|
| 226 | + }
|
| 227 | +
|
| 228 | + static public function refreshNotifyMe() {
|
| 229 | + wfProfileIn('SMWNotifyProcessor::refreshNotifyMe (SMW)');
|
| 230 | + $notifications = NMStorage::getDatabase()->getAllNotifications();
|
| 231 | + foreach ($notifications as $row) {
|
| 232 | + if($row['enable']) {
|
| 233 | + $result = SMWNotifyProcessor::enableNotify($row['notify_id'], $row['query']);
|
| 234 | + }
|
| 235 | + }
|
| 236 | + wfProfileOut('SMWNotifyProcessor::refreshNotifyMe (SMW)');
|
| 237 | + }
|
| 238 | +
|
| 239 | + static public function updateReportAll($notify_ids) {
|
| 240 | + wfProfileIn('SMWNotifyProcessor::updateStates (SMW)');
|
| 241 | +
|
| 242 | + $notifications = SMWNotifyProcessor::getNotifications();
|
| 243 | + if ($notifications == null || !is_array($notifications)) {
|
| 244 | + return "No notifications available.";
|
| 245 | + }
|
| 246 | + $result = true;
|
| 247 | + $idx = 0;
|
| 248 | + $count = count($notify_ids) - 1;
|
| 249 | + $sStore = NMStorage::getDatabase();
|
| 250 | + foreach ($notifications as $row) {
|
| 251 | + if(($idx<$count) && ($notify_ids[$idx]==$row['notify_id'])) {
|
| 252 | + if($row['rep_all']==0) {
|
| 253 | + $result = $sStore->updateNotifyReportAll($row['notify_id'], 1);
|
| 254 | + }
|
| 255 | + $idx ++;
|
| 256 | + } else {
|
| 257 | + if($row['rep_all']==1) {
|
| 258 | + $result = $sStore->updateNotifyReportAll($row['notify_id'], 0);
|
| 259 | + }
|
| 260 | + }
|
| 261 | + if(!$result) break;
|
| 262 | + }
|
| 263 | +
|
| 264 | + wfProfileOut('SMWNotifyProcessor::updateStates (SMW)');
|
| 265 | + return $result ? "States updated successfully!" : "States updated error!";
|
| 266 | + }
|
| 267 | +
|
| 268 | + static public function updateShowAll($notify_ids) {
|
| 269 | + wfProfileIn('SMWNotifyProcessor::updateStates (SMW)');
|
| 270 | +
|
| 271 | + $notifications = SMWNotifyProcessor::getNotifications();
|
| 272 | + if ($notifications == null || !is_array($notifications)) {
|
| 273 | + return "No notifications available.";
|
| 274 | + }
|
| 275 | + $result = true;
|
| 276 | + $idx = 0;
|
| 277 | + $count = count($notify_ids) - 1;
|
| 278 | + $sStore = NMStorage::getDatabase();
|
| 279 | + foreach ($notifications as $row) {
|
| 280 | + if(($idx<$count) && ($notify_ids[$idx]==$row['notify_id'])) {
|
| 281 | + if($row['show_all']==0) {
|
| 282 | + $result = $sStore->updateNotifyShowAll($row['notify_id'], 1);
|
| 283 | + }
|
| 284 | + $idx ++;
|
| 285 | + } else {
|
| 286 | + if($row['show_all']==1) {
|
| 287 | + $result = $sStore->updateNotifyShowAll($row['notify_id'], 0);
|
| 288 | + }
|
| 289 | + }
|
| 290 | + if(!$result) break;
|
| 291 | + }
|
| 292 | +
|
| 293 | + wfProfileOut('SMWNotifyProcessor::updateStates (SMW)');
|
| 294 | + return $result ? "States updated successfully!" : "States updated error!";
|
| 295 | + }
|
| 296 | +
|
| 297 | + static public function delNotify($notify_ids) {
|
| 298 | + wfProfileIn('SMWNotifyProcessor::delNotify (SMW)');
|
| 299 | + $sStore = NMStorage::getDatabase();
|
| 300 | + $result = $sStore->removeNotifyQuery($notify_ids);
|
| 301 | + wfProfileOut('SMWNotifyProcessor::delNotify (SMW)');
|
| 302 | + return $result ? "Notification(s) deleted successfully!" : "Notification(s) deleted error!";
|
| 303 | + }
|
| 304 | +
|
| 305 | + static protected $notifyJobs = array();
|
| 306 | + static public function prepareArticleSave($title) {
|
| 307 | + $page_id = $title->getArticleID();
|
| 308 | + if($page_id == 0) {
|
| 309 | + return;
|
| 310 | + }
|
| 311 | + $updates = SMWNotifyProcessor::$notifyJobs[$page_id];
|
| 312 | + if(empty($updates)) {
|
| 313 | + SMWNotifyProcessor::$notifyJobs[$page_id] = new SMWNotifyUpdate($title);
|
| 314 | + }
|
| 315 | + }
|
| 316 | + static public function articleSavedComplete($title) {
|
| 317 | + $page_id = $title->getArticleID();
|
| 318 | + if($page_id == 0) {
|
| 319 | + return;
|
| 320 | + }
|
| 321 | + $updates = SMWNotifyProcessor::$notifyJobs[$page_id];
|
| 322 | + if(empty($updates)) {
|
| 323 | + SMWNotifyProcessor::$notifyJobs[$page_id] = new SMWNotifyUpdate($title);
|
| 324 | + $updates = SMWNotifyProcessor::$notifyJobs[$page_id];
|
| 325 | + } else {
|
| 326 | + $updates->executeNotifyUpdate();
|
| 327 | + }
|
| 328 | + $updates->updateNotifyMonitor();
|
| 329 | + $updates->notifyUsers();
|
| 330 | + unset(SMWNotifyProcessor::$notifyJobs[$page_id]);
|
| 331 | + }
|
| 332 | + static public function articleDelete($title, $reason) {
|
| 333 | + $page_id = $title->getArticleID();
|
| 334 | + if($page_id == 0) {
|
| 335 | + return;
|
| 336 | + }
|
| 337 | + $updates = new SMWNotifyUpdate($title);
|
| 338 | + $updates->executeNotifyDelete($reason);
|
| 339 | + }
|
| 340 | + static public function toInfoId($type, $subquery, $attr_id) {
|
| 341 | + return base_convert(strval(($subquery << 8)|$type), 10, 9).'9'.$attr_id;
|
| 342 | + }
|
| 343 | + static public function getInfoFromId($id) {
|
| 344 | + $idx = strpos($id, '9');
|
| 345 | + $t = intval(base_convert(substr($id, 0, $idx), 9, 10));
|
| 346 | + return array(
|
| 347 | + 'type'=>$t&0xFF,
|
| 348 | + 'subquery'=>$t>>8,
|
| 349 | + 'attr_id'=>intval(substr($id, $idx+1))
|
| 350 | + );
|
| 351 | + }
|
| 352 | +
|
| 353 | +}
|
| 354 | +
|
| 355 | +// based on SMW_QueryProcessor.php (v 1.4.2)
|
| 356 | +/**
|
| 357 | + * Objects of this class are in charge of parsing a query string in order
|
| 358 | + * to create an SMWDescription. The class and methods are not static in order
|
| 359 | + * to more cleanly store the intermediate state and progress of the parser.
|
| 360 | + */
|
| 361 | +class SMWNotifyParser {
|
| 362 | +
|
| 363 | + protected $m_sepstack; // list of open blocks ("parentheses") that need closing at current step
|
| 364 | + protected $m_curstring; // remaining string to be parsed (parsing eats query string from the front)
|
| 365 | + var $m_errors; // empty array if all went right, array of strings otherwise
|
| 366 | + protected $m_label; //label of the main query result
|
| 367 | + protected $m_defaultns; //description of the default namespace restriction, or NULL if not used
|
| 368 | +
|
| 369 | + protected $m_categoryprefix; // cache label of category namespace . ':'
|
| 370 | + protected $m_conceptprefix; // cache label of concept namespace . ':'
|
| 371 | + protected $m_queryfeatures; // query features to be supported, format similar to $smwgQFeatures
|
| 372 | +
|
| 373 | + // added by dch
|
| 374 | + protected $m_notify_id;
|
| 375 | + protected $m_subquery;
|
| 376 | + protected $m_printoutArticles;
|
| 377 | + public $m_result;
|
| 378 | +
|
| 379 | + // modified by dch
|
| 380 | + public function SMWNotifyParser($notify_id, $printoutArticles, $queryfeatures = false) {
|
| 381 | + $this->m_notify_id = $notify_id;
|
| 382 | + $this->m_printoutArticles = $printoutArticles;
|
| 383 | + $this->m_result = true;
|
| 384 | +
|
| 385 | + global $wgContLang, $smwgQFeatures;
|
| 386 | + $this->m_categoryprefix = $wgContLang->getNsText(NS_CATEGORY) . ':';
|
| 387 | + $this->m_conceptprefix = $wgContLang->getNsText(SMW_NS_CONCEPT) . ':';
|
| 388 | + $this->m_defaultns = NULL;
|
| 389 | + $this->m_queryfeatures = $queryfeatures===false?$smwgQFeatures:$queryfeatures;
|
| 390 | + }
|
| 391 | +
|
| 392 | + // added by dch
|
| 393 | + public function hasSubquery() {
|
| 394 | + return $this->m_subquery > 1;
|
| 395 | + }
|
| 396 | +
|
| 397 | + /**
|
| 398 | + * Provide an array of namespace constants that are used as default restrictions.
|
| 399 | + * If NULL is given, no such default restrictions will be added (faster).
|
| 400 | + */
|
| 401 | + public function setDefaultNamespaces($nsarray) {
|
| 402 | + $this->m_defaultns = NULL;
|
| 403 | + if ($nsarray !== NULL) {
|
| 404 | + foreach ($nsarray as $ns) {
|
| 405 | + $this->m_defaultns = $this->addDescription($this->m_defaultns, new SMWNamespaceDescription($ns), false);
|
| 406 | + }
|
| 407 | + }
|
| 408 | + }
|
| 409 | +
|
| 410 | + /**
|
| 411 | + * Compute an SMWDescription from a query string. Returns whatever descriptions could be
|
| 412 | + * wrestled from the given string (the most general result being SMWThingDescription if
|
| 413 | + * no meaningful condition was extracted).
|
| 414 | + */
|
| 415 | + public function getQueryDescription($querystring) {
|
| 416 | + wfProfileIn('SMWNotifyParser::getQueryDescription (SMW)');
|
| 417 | + $this->m_errors = array();
|
| 418 | + $this->m_label = '';
|
| 419 | + $this->m_curstring = $querystring;
|
| 420 | + $this->m_sepstack = array();
|
| 421 | + $setNS = false;
|
| 422 | +
|
| 423 | + // added by dch
|
| 424 | + $this->m_subquery = 0;
|
| 425 | +
|
| 426 | + $result = $this->getSubqueryDescription($setNS, $this->m_label);
|
| 427 | + if (!$setNS) { // add default namespaces if applicable
|
| 428 | + $result = $this->addDescription($this->m_defaultns, $result);
|
| 429 | + }
|
| 430 | + if ($result === NULL) { // parsing went wrong, no default namespaces
|
| 431 | + $result = new SMWThingDescription();
|
| 432 | + }
|
| 433 | + wfProfileOut('SMWNotifyParser::getQueryDescription (SMW)');
|
| 434 | + return $result;
|
| 435 | + }
|
| 436 | +
|
| 437 | + /**
|
| 438 | + * Return array of error messages (possibly empty).
|
| 439 | + */
|
| 440 | + public function getErrors() {
|
| 441 | + return $this->m_errors;
|
| 442 | + }
|
| 443 | +
|
| 444 | + /**
|
| 445 | + * Return error message or empty string if no error occurred.
|
| 446 | + */
|
| 447 | + public function getErrorString() {
|
| 448 | + return smwfEncodeMessages($this->m_errors);
|
| 449 | + }
|
| 450 | +
|
| 451 | + /**
|
| 452 | + * Return label for the results of this query (which
|
| 453 | + * might be empty if no such information was passed).
|
| 454 | + */
|
| 455 | + public function getLabel() {
|
| 456 | + return $this->m_label;
|
| 457 | + }
|
| 458 | +
|
| 459 | +
|
| 460 | + /**
|
| 461 | + * Compute an SMWDescription for current part of a query, which should
|
| 462 | + * be a standalone query (the main query or a subquery enclosed within
|
| 463 | + * "\<q\>...\</q\>". Recursively calls similar methods and returns NULL upon error.
|
| 464 | + *
|
| 465 | + * The call-by-ref parameter $setNS is a boolean. Its input specifies whether
|
| 466 | + * the query should set the current default namespace if no namespace restrictions
|
| 467 | + * were given. If false, the calling super-query is happy to set the required
|
| 468 | + * NS-restrictions by itself if needed. Otherwise the subquery has to impose the defaults.
|
| 469 | + * This is so, since outermost queries and subqueries of disjunctions will have to set
|
| 470 | + * their own default restrictions.
|
| 471 | + *
|
| 472 | + * The return value of $setNS specifies whether or not the subquery has a namespace
|
| 473 | + * specification in place. This might happen automatically if the query string imposes
|
| 474 | + * such restrictions. The return value is important for those callers that otherwise
|
| 475 | + * set up their own restrictions.
|
| 476 | + *
|
| 477 | + * Note that $setNS is no means to switch on or off default namespaces in general,
|
| 478 | + * but just controls query generation. For general effect, the default namespaces
|
| 479 | + * should be set to NULL.
|
| 480 | + *
|
| 481 | + * The call-by-ref parameter $label is used to append any label strings found.
|
| 482 | + */
|
| 483 | + protected function getSubqueryDescription(&$setNS, &$label) {
|
| 484 | + global $smwgQPrintoutLimit;
|
| 485 | + wfLoadExtensionMessages('SemanticMediaWiki');
|
| 486 | + $conjunction = NULL; // used for the current inner conjunction
|
| 487 | + $disjuncts = array(); // (disjunctive) array of subquery conjunctions
|
| 488 | + $printrequests = array(); // the printrequests found for this query level
|
| 489 | + $hasNamespaces = false; // does the current $conjnuction have its own namespace restrictions?
|
| 490 | + $mustSetNS = $setNS; // must ns restrictions be set? (may become true even if $setNS is false)
|
| 491 | +
|
| 492 | + // added by dch
|
| 493 | + $subquery = $this->m_subquery;
|
| 494 | + if($subquery == 0) {
|
| 495 | + $relatedArticles = $this->m_printoutArticles;
|
| 496 | + } else {
|
| 497 | + $relatedArticles = array();
|
| 498 | + }
|
| 499 | + $this->m_subquery ++;
|
| 500 | +
|
| 501 | + $continue = ($chunk = $this->readChunk()) != ''; // skip empty subquery completely, thorwing an error
|
| 502 | + while ($continue) {
|
| 503 | + $setsubNS = false;
|
| 504 | + switch ($chunk) {
|
| 505 | + case '[[': // start new link block
|
| 506 | + // modified by dch
|
| 507 | + $ld = $this->getLinkDescription($setsubNS, $label, $relatedArticles);
|
| 508 | +
|
| 509 | + if ($ld instanceof SMWPrintRequest) {
|
| 510 | + $printrequests[] = $ld;
|
| 511 | + } elseif ($ld instanceof SMWDescription) {
|
| 512 | + $conjunction = $this->addDescription($conjunction,$ld);
|
| 513 | + }
|
| 514 | + break;
|
| 515 | + case '<q>': // enter new subquery, currently irrelevant but possible
|
| 516 | + $this->pushDelimiter('</q>');
|
| 517 | + $conjunction = $this->addDescription($conjunction, $this->getSubqueryDescription($setsubNS, $label));
|
| 518 | + /// TODO: print requests from subqueries currently are ignored, should be moved down
|
| 519 | + break;
|
| 520 | + case 'OR': case '||': case '': case '</q>': // finish disjunction and maybe subquery
|
| 521 | + if ($this->m_defaultns !== NULL) { // possibly add namespace restrictions
|
| 522 | + if ( $hasNamespaces && !$mustSetNS) {
|
| 523 | + // add ns restrictions to all earlier conjunctions (all of which did not have them yet)
|
| 524 | + $mustSetNS = true; // enforce NS restrictions from now on
|
| 525 | + $newdisjuncts = array();
|
| 526 | + foreach ($disjuncts as $conj) {
|
| 527 | + $newdisjuncts[] = $this->addDescription($conj, $this->m_defaultns);
|
| 528 | + }
|
| 529 | + $disjuncts = $newdisjuncts;
|
| 530 | + } elseif ( !$hasNamespaces && $mustSetNS) {
|
| 531 | + // add ns restriction to current result
|
| 532 | + $conjunction = $this->addDescription($conjunction, $this->m_defaultns);
|
| 533 | + }
|
| 534 | + }
|
| 535 | + $disjuncts[] = $conjunction;
|
| 536 | + // start anew
|
| 537 | + $conjunction = NULL;
|
| 538 | + $hasNamespaces = false;
|
| 539 | + // finish subquery?
|
| 540 | + if ($chunk == '</q>') {
|
| 541 | + if ($this->popDelimiter('</q>')) {
|
| 542 | + $continue = false; // leave the loop
|
| 543 | + } else {
|
| 544 | + $this->m_errors[] = wfMsgForContent('smw_toomanyclosing', $chunk);
|
| 545 | + return NULL;
|
| 546 | + }
|
| 547 | + } elseif ($chunk == '') {
|
| 548 | + $continue = false;
|
| 549 | + }
|
| 550 | + break;
|
| 551 | + case '+': // "... AND true" (ignore)
|
| 552 | + break;
|
| 553 | + default: // error: unexpected $chunk
|
| 554 | + $this->m_errors[] = wfMsgForContent('smw_unexpectedpart', $chunk);
|
| 555 | + //return NULL; // Try to go on, it can only get better ...
|
| 556 | + }
|
| 557 | + if ($setsubNS) { // namespace restrictions encountered in current conjunct
|
| 558 | + $hasNamespaces = true;
|
| 559 | + }
|
| 560 | + if ($continue) { // read on only if $continue remained true
|
| 561 | + $chunk = $this->readChunk();
|
| 562 | + }
|
| 563 | + }
|
| 564 | +
|
| 565 | + if (count($disjuncts) > 0) { // make disjunctive result
|
| 566 | + $result = NULL;
|
| 567 | + foreach ($disjuncts as $d) {
|
| 568 | + if ($d === NULL) {
|
| 569 | + $this->m_errors[] = wfMsgForContent('smw_emptysubquery');
|
| 570 | + $setNS = false;
|
| 571 | + return NULL;
|
| 572 | + } else {
|
| 573 | + $result = $this->addDescription($result, $d, false);
|
| 574 | + }
|
| 575 | + }
|
| 576 | + } else {
|
| 577 | + $this->m_errors[] = wfMsgForContent('smw_emptysubquery');
|
| 578 | + $setNS = false;
|
| 579 | + return NULL;
|
| 580 | + }
|
| 581 | + $setNS = $mustSetNS; // NOTE: also false if namespaces were given but no default NS descs are available
|
| 582 | +
|
| 583 | + $prcount = 0;
|
| 584 | + foreach ($printrequests as $pr) { // add printrequests
|
| 585 | + if ($prcount < $smwgQPrintoutLimit) {
|
| 586 | + $result->addPrintRequest($pr);
|
| 587 | + $prcount++;
|
| 588 | + } else {
|
| 589 | + $this->m_errors[] = wfMsgForContent('smw_overprintoutlimit');
|
| 590 | + break;
|
| 591 | + }
|
| 592 | + }
|
| 593 | +
|
| 594 | + // added by dch
|
| 595 | + if($this->m_result) {
|
| 596 | + $sStore = NMStorage::getDatabase();
|
| 597 | + $this->m_result = $sStore->addNotifyRelations($this->m_notify_id, $relatedArticles, $subquery);
|
| 598 | + }
|
| 599 | +
|
| 600 | + return $result;
|
| 601 | + }
|
| 602 | +
|
| 603 | + /**
|
| 604 | + * Compute an SMWDescription for current part of a query, which should
|
| 605 | + * be the content of "[[ ... ]]". Alternatively, if the current syntax
|
| 606 | + * specifies a print request, return the print request object.
|
| 607 | + * Returns NULL upon error.
|
| 608 | + *
|
| 609 | + * Parameters $setNS and $label have the same use as in getSubqueryDescription().
|
| 610 | + */
|
| 611 | + // modified by dch, add $relatedArticles
|
| 612 | + protected function getLinkDescription(&$setNS, &$label, &$relatedArticles) {
|
| 613 | + // This method is called when we encountered an opening '[['. The following
|
| 614 | + // block could be a Category-statement, fixed object, property statements,
|
| 615 | + // or according print statements.
|
| 616 | + $chunk = $this->readChunk('',true,false); // NOTE: untrimmed, initial " " escapes prop. chains
|
| 617 | + if ( (smwfNormalTitleText($chunk) == $this->m_categoryprefix) || // category statement or
|
| 618 | + (smwfNormalTitleText($chunk) == $this->m_conceptprefix) ) { // concept statement
|
| 619 | + return $this->getClassDescription($setNS, $label, $relatedArticles,
|
| 620 | + (smwfNormalTitleText($chunk) == $this->m_categoryprefix));
|
| 621 | + } else { // fixed subject, namespace restriction, property query, or subquery
|
| 622 | + $sep = $this->readChunk('',false); //do not consume hit, "look ahead"
|
| 623 | + if ( ($sep == '::') || ($sep == ':=') ) {
|
| 624 | + if ($chunk{0} !=':') { // property statement
|
| 625 | + return $this->getPropertyDescription($chunk, $setNS, $label, $relatedArticles);
|
| 626 | + } else { // escaped article description, read part after :: to get full contents
|
| 627 | + $chunk .= $this->readChunk('\[\[|\]\]|\|\||\|');
|
| 628 | + return $this->getArticleDescription(trim($chunk), $setNS, $label, $relatedArticles);
|
| 629 | + }
|
| 630 | + } else { // Fixed article/namespace restriction. $sep should be ]] or ||
|
| 631 | + return $this->getArticleDescription(trim($chunk), $setNS, $label, $relatedArticles);
|
| 632 | + }
|
| 633 | + }
|
| 634 | + }
|
| 635 | +
|
| 636 | + /**
|
| 637 | + * Parse a category description (the part of an inline query that
|
| 638 | + * is in between "[[Category:" and the closing "]]" and create a
|
| 639 | + * suitable description.
|
| 640 | + */
|
| 641 | + // modified by dch ,add $relatedArticles
|
| 642 | + protected function getClassDescription(&$setNS, &$label, &$relatedArticles, $category=true) {
|
| 643 | + global $smwgSMWBetaCompatible; // * printouts only for this old version
|
| 644 | + // note: no subqueries allowed here, inline disjunction allowed, wildcards allowed
|
| 645 | + $result = NULL;
|
| 646 | + $continue = true;
|
| 647 | + while ($continue) {
|
| 648 | + $chunk = $this->readChunk();
|
| 649 | + if ($chunk == '+') {
|
| 650 | + //wildcard, ignore for categories (semantically meaningless, everything is in some class)
|
| 651 | + } elseif ( ($chunk == '+') && $category && $smwgSMWBetaCompatible) { // print statement
|
| 652 | + $chunk = $this->readChunk('\]\]|\|');
|
| 653 | + if ($chunk == '|') {
|
| 654 | + $printlabel = $this->readChunk('\]\]');
|
| 655 | + if ($printlabel != ']]') {
|
| 656 | + $chunk = $this->readChunk('\]\]');
|
| 657 | + } else {
|
| 658 | + $printlabel = '';
|
| 659 | + $chunk = ']]';
|
| 660 | + }
|
| 661 | + } else {
|
| 662 | + global $wgContLang;
|
| 663 | + $printlabel = $wgContLang->getNSText(NS_CATEGORY);
|
| 664 | + }
|
| 665 | + if ($chunk == ']]') {
|
| 666 | + return new SMWPrintRequest(SMWPrintRequest::PRINT_CATS, $printlabel);
|
| 667 | + } else {
|
| 668 | + $this->m_errors[] = wfMsgForContent('smw_badprintout');
|
| 669 | + return NULL;
|
| 670 | + }
|
| 671 | + } else { //assume category/concept title
|
| 672 | + /// NOTE: use m_c...prefix to prevent problems with, e.g., [[Category:Template:Test]]
|
| 673 | + $class = Title::newFromText(($category?$this->m_categoryprefix:$this->m_conceptprefix) . $chunk);
|
| 674 | + if ($class !== NULL) {
|
| 675 | + $desc = $category?new SMWClassDescription($class):new SMWConceptDescription($class);
|
| 676 | + $result = $this->addDescription($result, $desc, false);
|
| 677 | + }
|
| 678 | +
|
| 679 | + // added by dch
|
| 680 | + if($category) {
|
| 681 | + $relatedArticles[] = array(
|
| 682 | + 'namespace' => NS_CATEGORY,
|
| 683 | + 'title' => $class->getDBkey());
|
| 684 | + }
|
| 685 | +
|
| 686 | + }
|
| 687 | + $chunk = $this->readChunk();
|
| 688 | + $continue = ($chunk == '||') && $category; // disjunctions only for cateories
|
| 689 | + }
|
| 690 | +
|
| 691 | + return $this->finishLinkDescription($chunk, false, $result, $setNS, $label);
|
| 692 | + }
|
| 693 | +
|
| 694 | + /**
|
| 695 | + * Parse a property description (the part of an inline query that
|
| 696 | + * is in between "[[Some property::" and the closing "]]" and create a
|
| 697 | + * suitable description. The "::" is the first chunk on the current
|
| 698 | + * string.
|
| 699 | + */
|
| 700 | + // modified by dch ,add $relatedArticles
|
| 701 | + protected function getPropertyDescription($propertyname, &$setNS, &$label, &$relatedArticles) {
|
| 702 | + global $smwgSMWBetaCompatible; // support for old * printouts of beta
|
| 703 | + wfLoadExtensionMessages('SemanticMediaWiki');
|
| 704 | + $this->readChunk(); // consume separator ":=" or "::"
|
| 705 | + // first process property chain syntax (e.g. "property1.property2::value"):
|
| 706 | + if ($propertyname{0} == ' ') { // escape
|
| 707 | + $propertynames = array($propertyname);
|
| 708 | + } else {
|
| 709 | + $propertynames = explode('.', $propertyname);
|
| 710 | + }
|
| 711 | + $properties = array();
|
| 712 | + $typeid = '_wpg';
|
| 713 | + foreach ($propertynames as $name) {
|
| 714 | + if ($typeid != '_wpg') { // non-final property in chain was no wikipage: not allowed
|
| 715 | + $this->m_errors[] = wfMsgForContent('smw_valuesubquery', $prevname);
|
| 716 | + return NULL; ///TODO: read some more chunks and try to finish [[ ]]
|
| 717 | + }
|
| 718 | + $property = SMWPropertyValue::makeUserProperty($name);
|
| 719 | + if (!$property->isValid()) { // illegal property identifier
|
| 720 | + $this->m_errors = array_merge($this->m_errors, $property->getErrors());
|
| 721 | + return NULL; ///TODO: read some more chunks and try to finish [[ ]]
|
| 722 | + }
|
| 723 | + $typeid = $property->getTypeID();
|
| 724 | + $prevname = $name;
|
| 725 | + $properties[] = $property;
|
| 726 | +
|
| 727 | + // added by dch
|
| 728 | + $relatedArticles[] = array(
|
| 729 | + 'namespace' => SMW_NS_PROPERTY,
|
| 730 | + 'title' => $property->getDBkey());
|
| 731 | +
|
| 732 | + } ///NOTE: after iteration, $property and $typeid correspond to last value
|
| 733 | +
|
| 734 | + $innerdesc = NULL;
|
| 735 | + $continue = true;
|
| 736 | + while ($continue) {
|
| 737 | + $chunk = $this->readChunk();
|
| 738 | + switch ($chunk) {
|
| 739 | + case '+': // wildcard, add namespaces for page-type properties
|
| 740 | + if ( ($this->m_defaultns !== NULL) && ($typeid == '_wpg') ) {
|
| 741 | + $innerdesc = $this->addDescription($innerdesc, $this->m_defaultns, false);
|
| 742 | + } else {
|
| 743 | + $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
|
| 744 | + }
|
| 745 | + $chunk = $this->readChunk();
|
| 746 | + break;
|
| 747 | + case '<q>': // subquery, set default namespaces
|
| 748 | + if ($typeid == '_wpg') {
|
| 749 | + $this->pushDelimiter('</q>');
|
| 750 | + $setsubNS = true;
|
| 751 | + $sublabel = '';
|
| 752 | + $innerdesc = $this->addDescription($innerdesc, $this->getSubqueryDescription($setsubNS, $sublabel), false);
|
| 753 | + } else { // no subqueries allowed for non-pages
|
| 754 | + $this->m_errors[] = wfMsgForContent('smw_valuesubquery', end($propertynames));
|
| 755 | + $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
|
| 756 | + }
|
| 757 | + $chunk = $this->readChunk();
|
| 758 | + break;
|
| 759 | + default: //normal object value or print statement
|
| 760 | + // read value(s), possibly with inner [[...]]
|
| 761 | + $open = 1;
|
| 762 | + $value = $chunk;
|
| 763 | + $continue2 = true;
|
| 764 | + // read value with inner [[, ]], ||
|
| 765 | + while ( ($open > 0) && ($continue2) ) {
|
| 766 | + $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
|
| 767 | + switch ($chunk) {
|
| 768 | + case '[[': // open new [[ ]]
|
| 769 | + $open++;
|
| 770 | + break;
|
| 771 | + case ']]': // close [[ ]]
|
| 772 | + $open--;
|
| 773 | + break;
|
| 774 | + case '|': case '||': // terminates only outermost [[ ]]
|
| 775 | + if ($open == 1) {
|
| 776 | + $open = 0;
|
| 777 | + }
|
| 778 | + break;
|
| 779 | + case '': ///TODO: report error; this is not good right now
|
| 780 | + $continue2 = false;
|
| 781 | + break;
|
| 782 | + }
|
| 783 | + if ($open != 0) {
|
| 784 | + $value .= $chunk;
|
| 785 | + }
|
| 786 | + } ///NOTE: at this point, we normally already read one more chunk behind the value
|
| 787 | +
|
| 788 | + if ($typeid == '__nry') { // nary value
|
| 789 | + $dv = SMWDataValueFactory::newPropertyObjectValue($property);
|
| 790 | + $dv->acceptQuerySyntax();
|
| 791 | + $dv->setUserValue($value);
|
| 792 | + $vl = $dv->getValueList();
|
| 793 | + $pm = $dv->getPrintModifier();
|
| 794 | + if ($vl !== NULL) { // prefer conditions over print statements (only one possible right now)
|
| 795 | + $innerdesc = $this->addDescription($innerdesc, $vl, false);
|
| 796 | + } elseif ($pm !== false) {
|
| 797 | + if ($chunk == '|') {
|
| 798 | + $printlabel = $this->readChunk('\]\]');
|
| 799 | + if ($printlabel != ']]') {
|
| 800 | + $chunk = $this->readChunk('\]\]');
|
| 801 | + } else {
|
| 802 | + $printlabel = '';
|
| 803 | + $chunk = ']]';
|
| 804 | + }
|
| 805 | + } else {
|
| 806 | + $printlabel = $property->getWikiValue();
|
| 807 | + }
|
| 808 | + if ($chunk == ']]') {
|
| 809 | + return new SMWPrintRequest(SMWPrintRequest::PRINT_PROP, $printlabel, $property, $pm);
|
| 810 | + } else {
|
| 811 | + $this->m_errors[] = wfMsgForContent('smw_badprintout');
|
| 812 | + return NULL;
|
| 813 | + }
|
| 814 | + }
|
| 815 | + } else { // unary value
|
| 816 | + $comparator = SMW_CMP_EQ;
|
| 817 | + $printmodifier = '';
|
| 818 | + SMWNotifyParser::prepareValue($value, $comparator, $printmodifier);
|
| 819 | + if ( ($value == '*') && $smwgSMWBetaCompatible ) {
|
| 820 | + if ($chunk == '|') {
|
| 821 | + $printlabel = $this->readChunk('\]\]');
|
| 822 | + if ($printlabel != ']]') {
|
| 823 | + $chunk = $this->readChunk('\]\]');
|
| 824 | + } else {
|
| 825 | + $printlabel = '';
|
| 826 | + $chunk = ']]';
|
| 827 | + }
|
| 828 | + } else {
|
| 829 | + $printlabel = $property->getWikiValue();
|
| 830 | + }
|
| 831 | + if ($chunk == ']]') {
|
| 832 | + return new SMWPrintRequest(SMWPrintRequest::PRINT_PROP, $printlabel, $property, $printmodifier);
|
| 833 | + } else {
|
| 834 | + $this->m_errors[] = wfMsgForContent('smw_badprintout');
|
| 835 | + return NULL;
|
| 836 | + }
|
| 837 | + } else {
|
| 838 | + $dv = SMWDataValueFactory::newPropertyObjectValue($property, $value);
|
| 839 | + if (!$dv->isValid()) {
|
| 840 | + $this->m_errors = $this->m_errors + $dv->getErrors();
|
| 841 | + $vd = new SMWThingDescription();
|
| 842 | + } else {
|
| 843 | + $vd = new SMWValueDescription($dv, $comparator);
|
| 844 | + }
|
| 845 | + $innerdesc = $this->addDescription($innerdesc, $vd, false);
|
| 846 | + }
|
| 847 | + }
|
| 848 | + }
|
| 849 | + $continue = ($chunk == '||');
|
| 850 | + }
|
| 851 | +
|
| 852 | + if ($innerdesc === NULL) { // make a wildcard search
|
| 853 | + if ( ($this->m_defaultns !== NULL) && ($typeid == '_wpg') ) {
|
| 854 | + $innerdesc = $this->addDescription($innerdesc, $this->m_defaultns, false);
|
| 855 | + } else {
|
| 856 | + $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
|
| 857 | + }
|
| 858 | + $this->m_errors[] = wfMsgForContent('smw_propvalueproblem', $property->getWikiValue());
|
| 859 | + }
|
| 860 | + $properties = array_reverse($properties);
|
| 861 | + foreach ($properties as $property) {
|
| 862 | + $innerdesc = new SMWSomeProperty($property,$innerdesc);
|
| 863 | + }
|
| 864 | + $result = $innerdesc;
|
| 865 | + return $this->finishLinkDescription($chunk, false, $result, $setNS, $label);
|
| 866 | + }
|
| 867 | +
|
| 868 | +
|
| 869 | + /**
|
| 870 | + * Prepare a single value string, possibly extracting comparators and
|
| 871 | + * printmodifier. $value is changed to consist only of the remaining
|
| 872 | + * effective value string, or of "*" for print statements.
|
| 873 | + */
|
| 874 | + static public function prepareValue(&$value, &$comparator, &$printmodifier) {
|
| 875 | + global $smwgQComparators, $smwgSMWBetaCompatible; // support for old * printouts of beta
|
| 876 | + // get print modifier behind *
|
| 877 | + if ($smwgSMWBetaCompatible) {
|
| 878 | + $list = preg_split('/^\*/',$value,2);
|
| 879 | + if (count($list) == 2) { //hit
|
| 880 | + $value = '*';
|
| 881 | + $printmodifier = $list[1];
|
| 882 | + } else {
|
| 883 | + $printmodifier = '';
|
| 884 | + }
|
| 885 | + if ($value == '*') { // printout statement
|
| 886 | + return;
|
| 887 | + }
|
| 888 | + }
|
| 889 | + $list = preg_split('/^(' . $smwgQComparators . ')/u',$value, 2, PREG_SPLIT_DELIM_CAPTURE);
|
| 890 | + $comparator = SMW_CMP_EQ;
|
| 891 | + if (count($list) == 3) { // initial comparator found ($list[1] should be empty)
|
| 892 | + switch ($list[1]) {
|
| 893 | + case '<':
|
| 894 | + $comparator = SMW_CMP_LEQ;
|
| 895 | + $value = $list[2];
|
| 896 | + break;
|
| 897 | + case '>':
|
| 898 | + $comparator = SMW_CMP_GEQ;
|
| 899 | + $value = $list[2];
|
| 900 | + break;
|
| 901 | + case '!':
|
| 902 | + $comparator = SMW_CMP_NEQ;
|
| 903 | + $value = $list[2];
|
| 904 | + break;
|
| 905 | + case '~':
|
| 906 | + $comparator = SMW_CMP_LIKE;
|
| 907 | + $value = $list[2];
|
| 908 | + break;
|
| 909 | + //default: not possible
|
| 910 | + }
|
| 911 | + }
|
| 912 | + }
|
| 913 | +
|
| 914 | + /**
|
| 915 | + * Parse an article description (the part of an inline query that
|
| 916 | + * is in between "[[" and the closing "]]" assuming it is not specifying
|
| 917 | + * a category or property) and create a suitable description.
|
| 918 | + * The first chunk behind the "[[" has already been read and is
|
| 919 | + * passed as a parameter.
|
| 920 | + */
|
| 921 | + // modified by dch ,add $relatedArticles
|
| 922 | + protected function getArticleDescription($firstchunk, &$setNS, &$label, &$relatedArticles) {
|
| 923 | + wfLoadExtensionMessages('SemanticMediaWiki');
|
| 924 | + $chunk = $firstchunk;
|
| 925 | + $result = NULL;
|
| 926 | + $continue = true;
|
| 927 | + //$innerdesc = NULL;
|
| 928 | + while ($continue) {
|
| 929 | + if ($chunk == '<q>') { // no subqueries of the form [[<q>...</q>]] (not needed)
|
| 930 | + $this->m_errors[] = wfMsgForContent('smw_misplacedsubquery');
|
| 931 | + return NULL;
|
| 932 | + }
|
| 933 | + $list = preg_split('/:/', $chunk, 3); // ":Category:Foo" "User:bar" ":baz" ":+"
|
| 934 | + if ( ($list[0] == '') && (count($list)==3) ) {
|
| 935 | + $list = array_slice($list, 1);
|
| 936 | + }
|
| 937 | + if ( (count($list) == 2) && ($list[1] == '+') ) { // try namespace restriction
|
| 938 | + global $wgContLang;
|
| 939 | + $idx = $wgContLang->getNsIndex($list[0]);
|
| 940 | + if ($idx !== false) {
|
| 941 | + $result = $this->addDescription($result, new SMWNamespaceDescription($idx), false);
|
| 942 | + }
|
| 943 | + } else {
|
| 944 | + $value = SMWDataValueFactory::newTypeIDValue('_wpg', $chunk);
|
| 945 | + if ($value->isValid()) {
|
| 946 | + $result = $this->addDescription($result, new SMWValueDescription($value), false);
|
| 947 | + // added by dch
|
| 948 | + $relatedArticles[] = array(
|
| 949 | + 'namespace' => NS_MAIN,
|
| 950 | + 'title' => Title::makeTitle( NS_MAIN, $chunk )->getDBkey());
|
| 951 | + }
|
| 952 | + }
|
| 953 | +
|
| 954 | + $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
|
| 955 | + if ($chunk == '||') {
|
| 956 | + $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
|
| 957 | + $continue = true;
|
| 958 | + } else {
|
| 959 | + $continue = false;
|
| 960 | + }
|
| 961 | + }
|
| 962 | +
|
| 963 | + return $this->finishLinkDescription($chunk, true, $result, $setNS, $label);
|
| 964 | + }
|
| 965 | +
|
| 966 | + protected function finishLinkDescription($chunk, $hasNamespaces, $result, &$setNS, &$label) {
|
| 967 | + wfLoadExtensionMessages('SemanticMediaWiki');
|
| 968 | + if ($result === NULL) { // no useful information or concrete error found
|
| 969 | + $this->m_errors[] = wfMsgForContent('smw_badqueryatom');
|
| 970 | + } elseif (!$hasNamespaces && $setNS && ($this->m_defaultns !== NULL) ) {
|
| 971 | + $result = $this->addDescription($result, $this->m_defaultns);
|
| 972 | + $hasNamespaces = true;
|
| 973 | + }
|
| 974 | + $setNS = $hasNamespaces;
|
| 975 | +
|
| 976 | + // terminate link (assuming that next chunk was read already)
|
| 977 | + if ($chunk == '|') {
|
| 978 | + $chunk = $this->readChunk('\]\]');
|
| 979 | + if ($chunk != ']]') {
|
| 980 | + $label .= $chunk;
|
| 981 | + $chunk = $this->readChunk('\]\]');
|
| 982 | + } else { // empty label does not add to overall label
|
| 983 | + $chunk = ']]';
|
| 984 | + }
|
| 985 | + }
|
| 986 | + if ($chunk != ']]') {
|
| 987 | + // What happended? We found some chunk that could not be processed as
|
| 988 | + // link content (as in [[Category:Test<q>]]) and there was no label to
|
| 989 | + // eat it. Or the closing ]] are just missing entirely.
|
| 990 | + if ($chunk != '') {
|
| 991 | + $this->m_errors[] = wfMsgForContent('smw_misplacedsymbol', htmlspecialchars($chunk));
|
| 992 | + // try to find a later closing ]] to finish this misshaped subpart
|
| 993 | + $chunk = $this->readChunk('\]\]');
|
| 994 | + if ($chunk != ']]') {
|
| 995 | + $chunk = $this->readChunk('\]\]');
|
| 996 | + }
|
| 997 | + }
|
| 998 | + if ($chunk == '') {
|
| 999 | + $this->m_errors[] = wfMsgForContent('smw_noclosingbrackets');
|
| 1000 | + }
|
| 1001 | + }
|
| 1002 | + return $result;
|
| 1003 | + }
|
| 1004 | +
|
| 1005 | + /**
|
| 1006 | + * Get the next unstructured string chunk from the query string.
|
| 1007 | + * Chunks are delimited by any of the special strings used in inline queries
|
| 1008 | + * (such as [[, ]], <q>, ...). If the string starts with such a delimiter,
|
| 1009 | + * this delimiter is returned. Otherwise the first string in front of such a
|
| 1010 | + * delimiter is returned.
|
| 1011 | + * Trailing and initial spaces are ignored if $trim is true, and chunks
|
| 1012 | + * consisting only of spaces are not returned.
|
| 1013 | + * If there is no more qurey string left to process, the empty string is
|
| 1014 | + * returned (and in no other case).
|
| 1015 | + *
|
| 1016 | + * The stoppattern can be used to customise the matching, especially in order to
|
| 1017 | + * overread certain special symbols.
|
| 1018 | + *
|
| 1019 | + * $consume specifies whether the returned chunk should be removed from the
|
| 1020 | + * query string.
|
| 1021 | + */
|
| 1022 | + protected function readChunk($stoppattern = '', $consume=true, $trim=true) {
|
| 1023 | + if ($stoppattern == '') {
|
| 1024 | + $stoppattern = '\[\[|\]\]|::|:=|<q>|<\/q>|^' . $this->m_categoryprefix .
|
| 1025 | + '|^' . $this->m_conceptprefix . '|\|\||\|';
|
| 1026 | + }
|
| 1027 | + $chunks = preg_split('/[\s]*(' . $stoppattern . ')/u', $this->m_curstring, 2, PREG_SPLIT_DELIM_CAPTURE);
|
| 1028 | + if (count($chunks) == 1) { // no matches anymore, strip spaces and finish
|
| 1029 | + if ($consume) {
|
| 1030 | + $this->m_curstring = '';
|
| 1031 | + }
|
| 1032 | + return $trim?trim($chunks[0]):$chunks[0];
|
| 1033 | + } elseif (count($chunks) == 3) { // this should generally happen if count is not 1
|
| 1034 | + if ($chunks[0] == '') { // string started with delimiter
|
| 1035 | + if ($consume) {
|
| 1036 | + $this->m_curstring = $chunks[2];
|
| 1037 | + }
|
| 1038 | + return $trim?trim($chunks[1]):$chunks[1];
|
| 1039 | + } else {
|
| 1040 | + if ($consume) {
|
| 1041 | + $this->m_curstring = $chunks[1] . $chunks[2];
|
| 1042 | + }
|
| 1043 | + return $trim?trim($chunks[0]):$chunks[0];
|
| 1044 | + }
|
| 1045 | + } else { return false; } //should never happen
|
| 1046 | + }
|
| 1047 | +
|
| 1048 | + /**
|
| 1049 | + * Enter a new subblock in the query, which must at some time be terminated by the
|
| 1050 | + * given $endstring delimiter calling popDelimiter();
|
| 1051 | + */
|
| 1052 | + protected function pushDelimiter($endstring) {
|
| 1053 | + array_push($this->m_sepstack, $endstring);
|
| 1054 | + }
|
| 1055 | +
|
| 1056 | + /**
|
| 1057 | + * Exit a subblock in the query ending with the given delimiter.
|
| 1058 | + * If the delimiter does not match the top-most open block, false
|
| 1059 | + * will be returned. Otherwise return true.
|
| 1060 | + */
|
| 1061 | + protected function popDelimiter($endstring) {
|
| 1062 | + $topdelim = array_pop($this->m_sepstack);
|
| 1063 | + return ($topdelim == $endstring);
|
| 1064 | + }
|
| 1065 | +
|
| 1066 | + /**
|
| 1067 | + * Extend a given description by a new one, either by adding the new description
|
| 1068 | + * (if the old one is a container description) or by creating a new container.
|
| 1069 | + * The parameter $conjunction determines whether the combination of both descriptions
|
| 1070 | + * should be a disjunction or conjunction.
|
| 1071 | + *
|
| 1072 | + * In the special case that the current description is NULL, the new one will just
|
| 1073 | + * replace the current one.
|
| 1074 | + *
|
| 1075 | + * The return value is the expected combined description. The object $curdesc will
|
| 1076 | + * also be changed (if it was non-NULL).
|
| 1077 | + */
|
| 1078 | + protected function addDescription($curdesc, $newdesc, $conjunction = true) {
|
| 1079 | + wfLoadExtensionMessages('SemanticMediaWiki');
|
| 1080 | + $notallowedmessage = 'smw_noqueryfeature';
|
| 1081 | + if ($newdesc instanceof SMWSomeProperty) {
|
| 1082 | + $allowed = $this->m_queryfeatures & SMW_PROPERTY_QUERY;
|
| 1083 | + } elseif ($newdesc instanceof SMWClassDescription) {
|
| 1084 | + $allowed = $this->m_queryfeatures & SMW_CATEGORY_QUERY;
|
| 1085 | + } elseif ($newdesc instanceof SMWConceptDescription) {
|
| 1086 | + $allowed = $this->m_queryfeatures & SMW_CONCEPT_QUERY;
|
| 1087 | + } elseif ($newdesc instanceof SMWConjunction) {
|
| 1088 | + $allowed = $this->m_queryfeatures & SMW_CONJUNCTION_QUERY;
|
| 1089 | + $notallowedmessage = 'smw_noconjunctions';
|
| 1090 | + } elseif ($newdesc instanceof SMWDisjunction) {
|
| 1091 | + $allowed = $this->m_queryfeatures & SMW_DISJUNCTION_QUERY;
|
| 1092 | + $notallowedmessage = 'smw_nodisjunctions';
|
| 1093 | + } else {
|
| 1094 | + $allowed = true;
|
| 1095 | + }
|
| 1096 | + if (!$allowed) {
|
| 1097 | + $this->m_errors[] = wfMsgForContent($notallowedmessage, str_replace('[', '[', $newdesc->getQueryString()));
|
| 1098 | + return $curdesc;
|
| 1099 | + }
|
| 1100 | + if ($newdesc === NULL) {
|
| 1101 | + return $curdesc;
|
| 1102 | + } elseif ($curdesc === NULL) {
|
| 1103 | + return $newdesc;
|
| 1104 | + } else { // we already found descriptions
|
| 1105 | + if ( (($conjunction) && ($curdesc instanceof SMWConjunction)) ||
|
| 1106 | + ((!$conjunction) && ($curdesc instanceof SMWDisjunction)) ) { // use existing container
|
| 1107 | + $curdesc->addDescription($newdesc);
|
| 1108 | + return $curdesc;
|
| 1109 | + } elseif ($conjunction) { // make new conjunction
|
| 1110 | + if ($this->m_queryfeatures & SMW_CONJUNCTION_QUERY) {
|
| 1111 | + return new SMWConjunction(array($curdesc,$newdesc));
|
| 1112 | + } else {
|
| 1113 | + $this->m_errors[] = wfMsgForContent('smw_noconjunctions', str_replace('[', '[', $newdesc->getQueryString()));
|
| 1114 | + return $curdesc;
|
| 1115 | + }
|
| 1116 | + } else { // make new disjunction
|
| 1117 | + if ($this->m_queryfeatures & SMW_DISJUNCTION_QUERY) {
|
| 1118 | + return new SMWDisjunction(array($curdesc,$newdesc));
|
| 1119 | + } else {
|
| 1120 | + $this->m_errors[] = wfMsgForContent('smw_nodisjunctions', str_replace('[', '[', $newdesc->getQueryString()));
|
| 1121 | + return $curdesc;
|
| 1122 | + }
|
| 1123 | + }
|
| 1124 | + }
|
| 1125 | + }
|
| 1126 | +}
|
| 1127 | +
|
| 1128 | +class SMWNotifyUpdate {
|
| 1129 | + protected $m_info;
|
| 1130 | + protected $m_title;
|
| 1131 | + protected $m_userMsgs;
|
| 1132 | + protected $m_userHtmlPropMsgs;
|
| 1133 | + protected $m_userHtmlNMMsgs;
|
| 1134 | + protected $m_userNMs;
|
| 1135 | + protected $m_notifyHtmlPropMsgs;
|
| 1136 | + protected $m_notifyHtmlMsgs;
|
| 1137 | + protected $m_newMonitor;
|
| 1138 | + protected $m_removeMonitored;
|
| 1139 | + protected $m_subQueryNotify;
|
| 1140 | +
|
| 1141 | + protected $m_linker;
|
| 1142 | +
|
| 1143 | + protected function getSemanticInfo($title) {
|
| 1144 | + $result = array();
|
| 1145 | + $sStore = NMStorage::getDatabase();
|
| 1146 | + $semdata = smwfGetStore()->getSemanticData($title);
|
| 1147 | + foreach($semdata->getProperties() as $property) {
|
| 1148 | + if (!$property->isShown() && $property->getWikiValue()!='') { // showing this is not desired, hide
|
| 1149 | + continue;
|
| 1150 | + } elseif ($property->isUserDefined()) { // user defined property
|
| 1151 | + $property->setCaption(preg_replace('/[ ]/u',' ',$property->getWikiValue(),2));
|
| 1152 | + }
|
| 1153 | +
|
| 1154 | + $propvalues = $semdata->getPropertyValues($property);
|
| 1155 | + if($property->getWikiValue()!='') {
|
| 1156 | + foreach ($propvalues as $propvalue) {
|
| 1157 | + if($propvalue->getXSDValue() != '') {
|
| 1158 | + $result[SMWNotifyProcessor::toInfoId(2,0,$sStore->lookupSmwId(SMW_NS_PROPERTY, $property->getXSDValue()))][] = array('name'=>$property, 'value'=>$propvalue);
|
| 1159 | + }
|
| 1160 | + }
|
| 1161 | + } else {
|
| 1162 | + foreach ($propvalues as $propvalue) {
|
| 1163 | + if(($propvalue instanceof SMWWikiPageValue)&&($propvalue->getNamespace() == NS_CATEGORY)) {
|
| 1164 | + $result[SMWNotifyProcessor::toInfoId(0,0,$sStore->lookupSmwId(NS_CATEGORY, $propvalue->getXSDValue()))][] = array('name'=>$propvalue, 'value'=>null);
|
| 1165 | + }
|
| 1166 | + }
|
| 1167 | + }
|
| 1168 | + }
|
| 1169 | + return $result;
|
| 1170 | + }
|
| 1171 | +
|
| 1172 | + public function SMWNotifyUpdate($title) {
|
| 1173 | + $this->m_title = $title;
|
| 1174 | + $this->m_userMsgs = array();
|
| 1175 | + $this->m_userHtmlPropMsgs = array();
|
| 1176 | + $this->m_userHtmlNMMsgs = array();
|
| 1177 | + $this->m_userNMs = array();
|
| 1178 | + $this->m_notifyHtmlPropMsgs = array();
|
| 1179 | + $this->m_notifyHtmlMsgs = array();
|
| 1180 | + $this->m_newMonitor = array();
|
| 1181 | + $this->m_removeMonitored = array();
|
| 1182 | + $this->m_subQueryNotify = array();
|
| 1183 | + $this->m_linker = new Linker();
|
| 1184 | +
|
| 1185 | + $page_id = $title->getArticleID();
|
| 1186 | + if(($page_id == 0) || ($this->m_title->getNamespace() != NS_MAIN)) {
|
| 1187 | + return;
|
| 1188 | + }
|
| 1189 | + $this->m_info = $this->getSemanticInfo($this->m_title);
|
| 1190 | + }
|
| 1191 | + public function executeNotifyDelete($reason) {
|
| 1192 | + $page_id = $this->m_title->getArticleID();
|
| 1193 | + if(($page_id == 0) || ($this->m_title->getNamespace() != NS_MAIN)) {
|
| 1194 | + return;
|
| 1195 | + }
|
| 1196 | + $page_name = $this->m_title->getText().' ('.$this->m_title->getFullUrl().')';
|
| 1197 | + $page_html_name = '<a href="'.$this->m_title->getFullUrl().'">'.htmlspecialchars($this->m_title->getText()).'</a>';
|
| 1198 | +
|
| 1199 | + $msg .= "\r\nPage $page_name has been deleted.\r\nReason : $reason";
|
| 1200 | + $sStore = NMStorage::getDatabase();
|
| 1201 | + $notifications = $sStore->getMonitoredNotifications($page_id);
|
| 1202 | +
|
| 1203 | + foreach($notifications as $user_id=>$notifies) {
|
| 1204 | + $this->m_userMsgs[$user_id] .= $msg.'\r\n( NM: ';
|
| 1205 | + $hint = "<P>Page $page_html_name has been deleted.<br/>Reason : <font color='red'>".htmlspecialchars($reason)."</font></P>";
|
| 1206 | + $this->m_userHtmlNMMsgs[$user_id] .= "$hint<P>Notify Me: ";
|
| 1207 | + $idx = 0;
|
| 1208 | + foreach($notifies as $notify_id=>$notify_detail) {
|
| 1209 | + if($idx>0) {
|
| 1210 | + $this->m_userMsgs[$user_id] .= ', ';
|
| 1211 | + $this->m_userHtmlNMMsgs[$user_id] .= ', ';
|
| 1212 | + }
|
| 1213 | + $this->m_notifyHtmlMsgs[$notify_id] .= $hint;
|
| 1214 | +
|
| 1215 | + $this->m_userMsgs[$user_id] .= $notify_detail['name'];
|
| 1216 | + $this->m_userHtmlNMMsgs[$user_id] .= '<b>'.htmlspecialchars($notify_detail['name']).'</b>';
|
| 1217 | + $idx++;
|
| 1218 | + $this->m_removeMonitored[] = array('notify_id'=>$notify_id, 'page_id'=>$page_id);
|
| 1219 | +
|
| 1220 | + $this->m_userNMs[$user_id][] = $notify_id;
|
| 1221 | + }
|
| 1222 | +
|
| 1223 | + $this->m_userMsgs[$user_id] .= " ).";
|
| 1224 | + $this->m_userHtmlNMMsgs[$user_id] .= " does not match this page now.</P>";
|
| 1225 | + }
|
| 1226 | +
|
| 1227 | + $this->notifyUsers();
|
| 1228 | + $sStore->removeNotifyMonitor($this->m_removeMonitored);
|
| 1229 | + }
|
| 1230 | + function isEqual($v1, $v2) {
|
| 1231 | + return (strval($v1[value]->getXSDValue()) == strval($v2[value]->getXSDValue()));
|
| 1232 | + }
|
| 1233 | + function getNotifyPlain($info, $key){
|
| 1234 | + $i = SMWNotifyProcessor::getInfoFromId($key);
|
| 1235 | + if($i[type]==0) {
|
| 1236 | + return "\r\n'".$info[name]->getWikiValue()."' has been ".($info[sem_act]==0?"deleted":"added").".";
|
| 1237 | + } else {
|
| 1238 | + $tmp = "\r\nProperty '".$info[name]->getWikiValue()."' has been ".($info[sem_act]==0?"deleted.":($info[sem_act]==1?"modified":"added")).".";
|
| 1239 | + $first = true;
|
| 1240 | + foreach($info[del_vals] as $val) {
|
| 1241 | + if($first) {
|
| 1242 | + $tmp .= "\r\nValue '";
|
| 1243 | + $first = false;
|
| 1244 | + } else {
|
| 1245 | + $tmp .= "', '";
|
| 1246 | + }
|
| 1247 | + $tmp .= $val[plain];
|
| 1248 | + }
|
| 1249 | + if(!$first) {
|
| 1250 | + $tmp .= "' deleted.";
|
| 1251 | + }
|
| 1252 | + $first = true;
|
| 1253 | + foreach($info[new_vals] as $val) {
|
| 1254 | + if($first) {
|
| 1255 | + $tmp .= "\r\nValue '";
|
| 1256 | + $first = false;
|
| 1257 | + } else {
|
| 1258 | + $tmp .= "', '";
|
| 1259 | + }
|
| 1260 | + $tmp .= $val[plain];
|
| 1261 | + }
|
| 1262 | + if(!$first) {
|
| 1263 | + $tmp .= "' added.";
|
| 1264 | + }
|
| 1265 | + return $tmp."\r\n";
|
| 1266 | + }
|
| 1267 | + }
|
| 1268 | + function getNotifyHtml($info, $key){
|
| 1269 | + $i = SMWNotifyProcessor::getInfoFromId($key);
|
| 1270 | + if($i[type]==0) {
|
| 1271 | + return "<td>Category</td>
|
| 1272 | + <td>".$info[name]->getShortHTMLText($this->m_linker)."</td>
|
| 1273 | + <td>".($info[sem_act]==0?"<font color='green'>remove</font>":"<font color='red'>cite</font>")."</td>
|
| 1274 | + <td colspan='2'>N/A</td>";
|
| 1275 | + } else {
|
| 1276 | + $rows = max(count($info[del_vals]), count($info[new_vals]));
|
| 1277 | + $tmp = "<tr><td rowspan='$rows'>Property</td>
|
| 1278 | + <td rowspan='$rows'>".$info[name]->getShortHTMLText($this->m_linker)."</td>
|
| 1279 | + <td rowspan='$rows'>".($info[sem_act]==0?"<font color='green'>remove</font>":($info[sem_act]==1?"<font color='blue'>modify</font>":"<font color='red'>cite</font>"))."</td>";
|
| 1280 | + for($idx=0;$idx<$rows;++$idx) {
|
| 1281 | + if($idx>0){
|
| 1282 | + $tmp .= "<tr>";
|
| 1283 | + }
|
| 1284 | + $tmp .= "<td>".(isset($info[del_vals][$idx])?$info[del_vals][$idx][html]:" ")."</td>
|
| 1285 | + <td>".(isset($info[new_vals][$idx])?$info[new_vals][$idx][html]:" ")."</td>
|
| 1286 | + </tr>";
|
| 1287 | + }
|
| 1288 | + return $tmp;
|
| 1289 | + }
|
| 1290 | + }
|
| 1291 | + public function executeNotifyUpdate() {
|
| 1292 | + $page_id = $this->m_title->getArticleID();
|
| 1293 | + if(($page_id == 0) || ($this->m_title->getNamespace() != NS_MAIN)) {
|
| 1294 | + return;
|
| 1295 | + }
|
| 1296 | + $sStore = NMStorage::getDatabase();
|
| 1297 | +
|
| 1298 | + $info = $this->getSemanticInfo($this->m_title);
|
| 1299 | + // get different
|
| 1300 | + $tmp_info = array(); // type : category 0, property 2; name; sem action : del 0, modify 1, add 2; val action : del 0, add 1
|
| 1301 | + foreach($this->m_info as $key=>$value) {
|
| 1302 | + $i = SMWNotifyProcessor::getInfoFromId($key);
|
| 1303 | + $updated = false;
|
| 1304 | + if(!isset($info[$key])) {
|
| 1305 | + if($i[type]==0) {
|
| 1306 | + $tmp_info[$key] = array('sem_act' => 0, 'name'=>$value[0][name]);
|
| 1307 | + }else {
|
| 1308 | + $tmp_info[$key] = array('sem_act'=>0, 'name'=>$value[0][name], 'del_vals'=>array(), 'new_vals'=>array());
|
| 1309 | + foreach($value as $v) {
|
| 1310 | + $tmp_info[$key][del_vals][] = array( 'plain' => $v[value]->getWikiValue(), 'html' => $v[value]->getShortHTMLText($this->m_linker));
|
| 1311 | + }
|
| 1312 | + }
|
| 1313 | + } else if($i[type] == 2) {
|
| 1314 | + $mvalue = $info[$key];
|
| 1315 | + foreach($value as $v1) {
|
| 1316 | + $found = false;
|
| 1317 | + foreach($mvalue as $v2) {
|
| 1318 | + if($this->isEqual($v1, $v2)) {
|
| 1319 | + $found = true;
|
| 1320 | + break;
|
| 1321 | + }
|
| 1322 | + }
|
| 1323 | + if(!$found) {
|
| 1324 | + if(!$updated){
|
| 1325 | + $updated = true;
|
| 1326 | + $tmp_info[$key] = array('sem_act'=>1, 'name'=>$value[0][name], 'del_vals'=>array(), 'new_vals'=>array());
|
| 1327 | + }
|
| 1328 | + $tmp_info[$key][del_vals][] = array( 'plain' => $v1[value]->getWikiValue(), 'html' => $v1[value]->getShortHTMLText($this->m_linker));
|
| 1329 | + }
|
| 1330 | + }
|
| 1331 | + foreach($mvalue as $v1) {
|
| 1332 | + $found = false;
|
| 1333 | + foreach($value as $v2) {
|
| 1334 | + if($this->isEqual($v1, $v2)) {
|
| 1335 | + $found = true;
|
| 1336 | + break;
|
| 1337 | + }
|
| 1338 | + }
|
| 1339 | + if(!$found) {
|
| 1340 | + if(!$updated){
|
| 1341 | + $updated = true;
|
| 1342 | + $tmp_info[$key] = array('sem_act'=>1, 'name'=>$value[0][name], 'del_vals'=>array(), 'new_vals'=>array());
|
| 1343 | + }
|
| 1344 | + $tmp_info[$key][new_vals][] = array( 'plain' => $v1[value]->getWikiValue(), 'html' => $v1[value]->getShortHTMLText($this->m_linker));
|
| 1345 | + }
|
| 1346 | + }
|
| 1347 | + }
|
| 1348 | + }
|
| 1349 | + foreach($info as $key=>$value) {
|
| 1350 | + $i = SMWNotifyProcessor::getInfoFromId($key);
|
| 1351 | + if(!isset($this->m_info[$key])) {
|
| 1352 | + if($i[type]==0) {
|
| 1353 | + $tmp_info[$key] = array('sem_act' => 2, 'name'=>$value[0][name]);
|
| 1354 | + }else {
|
| 1355 | + $tmp_info[$key] = array('sem_act'=>2, 'name'=>$value[0][name], 'del_vals'=>array(), 'new_vals'=>array());
|
| 1356 | + foreach($value as $v) {
|
| 1357 | + $tmp_info[$key][new_vals][] = array( 'plain' => $v[value]->getWikiValue(), 'html' => $v[value]->getShortHTMLText($this->m_linker));
|
| 1358 | + }
|
| 1359 | + }
|
| 1360 | + }
|
| 1361 | + }
|
| 1362 | +
|
| 1363 | + $notifications = $sStore->getMonitoredNotificationsDetail($page_id);
|
| 1364 | + // add semantic info to report all NM
|
| 1365 | + foreach($notifications as $user_id=>$notifies) {
|
| 1366 | + foreach($notifies['rep_all'] as $notify_id=>$notify_name) {
|
| 1367 | + foreach(array_keys($tmp_info) as $key) {
|
| 1368 | + $notifications[$user_id]['semantic'][$key][$notify_id]=$notify_name;
|
| 1369 | + }
|
| 1370 | + }
|
| 1371 | + }
|
| 1372 | + $page_name = $this->m_title->getText().' ('.$this->m_title->getFullUrl().')';
|
| 1373 | +
|
| 1374 | + foreach($notifications as $user_id=>$notifies) {
|
| 1375 | + foreach($notifies['semantic'] as $key=>$notify) {
|
| 1376 | + if(isset($tmp_info[$key])) {
|
| 1377 | + $hint = "";
|
| 1378 | + if(!isset($this->m_userMsgs[$user_id])) {
|
| 1379 | + $this->m_userMsgs[$user_id] = "\r\nSemantic attributes are changed in page $page_name.";
|
| 1380 | + $hint = "Semantic attributes are changed in page <a href='".$this->m_title->getFullUrl()."'>".htmlspecialchars($this->m_title->getText())."</a>.<br/>";
|
| 1381 | + $this->m_userHtmlNMMsgs[$user_id] .= $hint;
|
| 1382 | + }
|
| 1383 | +
|
| 1384 | + $this->m_userMsgs[$user_id] .= $this->getNotifyPlain($tmp_info[$key],$key).' ( NM: ';
|
| 1385 | + $propHint = $this->getNotifyHtml($tmp_info[$key],$key);
|
| 1386 | + $this->m_userHtmlPropMsgs[$user_id] .= $propHint . "<tr><td colspan='5'>Notify Me: ";
|
| 1387 | + $idx = 0;
|
| 1388 | + foreach($notify as $notify_id=>$notify_name) {
|
| 1389 | + if($idx>0) {
|
| 1390 | + $this->m_userMsgs[$user_id] .= ', ';
|
| 1391 | + $this->m_userHtmlPropMsgs[$user_id] .= ', ';
|
| 1392 | + }
|
| 1393 | + $this->m_userMsgs[$user_id] .= $notify_name;
|
| 1394 | + $this->m_userHtmlPropMsgs[$user_id] .= '<b>'.htmlspecialchars($notify_name).'</b>';
|
| 1395 | + $this->m_notifyHtmlMsgs[$notify_id] .= $hint;
|
| 1396 | + $this->m_notifyHtmlPropMsgs[$notify_id] .= $propHint;
|
| 1397 | + $idx++;
|
| 1398 | +
|
| 1399 | + $this->m_userNMs[$user_id][] = $notify_id;
|
| 1400 | + }
|
| 1401 | + $this->m_userMsgs[$user_id] .= ' ).';
|
| 1402 | + $this->m_userHtmlPropMsgs[$user_id] .= "</td></tr>";
|
| 1403 | + }
|
| 1404 | + }
|
| 1405 | + }
|
| 1406 | + // get possible subquery
|
| 1407 | + $this->m_subQueryNotify = array();
|
| 1408 | + $queries = $sStore->getPossibleQuery($this->m_info);
|
| 1409 | + if(is_array($queries)) {
|
| 1410 | + foreach($queries[1] as $notify_id=>$notify) {
|
| 1411 | + $this->m_subQueryNotify[$notify_id] = $notify;
|
| 1412 | + }
|
| 1413 | + }
|
| 1414 | +
|
| 1415 | + $this->m_info = $info;
|
| 1416 | + }
|
| 1417 | +
|
| 1418 | + // this will cost time, think we can update monitor in a single thread, like Job
|
| 1419 | + public function updateNotifyMonitor() {
|
| 1420 | + $page_id = $this->m_title->getArticleID();
|
| 1421 | + if(($page_id == 0) || ($this->m_title->getNamespace() != NS_MAIN)) {
|
| 1422 | + return;
|
| 1423 | + }
|
| 1424 | + $sStore = NMStorage::getDatabase();
|
| 1425 | + $queries = $sStore->getPossibleQuery($this->m_info);
|
| 1426 | + if(!is_array($queries)) {
|
| 1427 | + return;
|
| 1428 | + }
|
| 1429 | + // get monitored query
|
| 1430 | + $main_queries = $sStore->getMonitoredQuery($page_id);
|
| 1431 | + foreach($queries[0] as $notify_id=>$notify) {
|
| 1432 | + $main_queries[$notify_id] = $notify;
|
| 1433 | + }
|
| 1434 | +
|
| 1435 | + // begin notify query on main query
|
| 1436 | + $page_name = $this->m_title->getText().' ('.$this->m_title->getFullUrl().')';
|
| 1437 | + $page_html_name = '<a href="'.$this->m_title->getFullUrl().'">'.htmlspecialchars($this->m_title->getText()).'</a>';
|
| 1438 | +
|
| 1439 | + foreach($main_queries as $notify_id=>$notify) {
|
| 1440 | + $sStore->getNotifyInMainQuery($page_id, $notify_id, $notify['sql'], $notify['hierarchy'], $match, $monitoring);
|
| 1441 | + if((!$monitoring) && $match) {
|
| 1442 | + $this->m_userMsgs[$notify['user_id']] .= "\r\nPage $page_name matches NotifyMe '$notify[name]' now.";
|
| 1443 | + $hint = "Page $page_html_name matches \"<b>".htmlspecialchars($notify[name])."</b>\" now.<br/>";
|
| 1444 | + $this->m_userHtmlNMMsgs[$notify['user_id']] .= $hint;
|
| 1445 | + $this->m_notifyHtmlMsgs[$notify_id] .= $hint;
|
| 1446 | + $this->m_newMonitor[] = array('notify_id'=>$notify_id, 'page_id'=>$page_id);
|
| 1447 | + } else if((!$match) && $monitoring) {
|
| 1448 | + $this->m_userMsgs[$notify['user_id']] .= "\r\nPage $page_name does not match NotifyMe '$notify[name]' now.";
|
| 1449 | + $hint = "Page $page_html_name does not match \"<b>".htmlspecialchars($notify[name])."</b>\" now.<br/>";
|
| 1450 | + $this->m_userHtmlNMMsgs[$notify['user_id']] .= $hint;
|
| 1451 | + $this->m_notifyHtmlMsgs[$notify_id] .= $hint;
|
| 1452 | + $this->m_removeMonitored[] = array('notify_id'=>$notify_id, 'page_id'=>$page_id);
|
| 1453 | + }
|
| 1454 | + $this->m_userNMs[$notify['user_id']][] = $notify_id;
|
| 1455 | + }
|
| 1456 | + // begin notify query on sub query, should go through all pages
|
| 1457 | + foreach($queries[1] as $notify_id=>$notify) {
|
| 1458 | + $this->m_subQueryNotify[$notify_id] = $notify;
|
| 1459 | + }
|
| 1460 | + foreach($this->m_subQueryNotify as $notify_id=>$notify) {
|
| 1461 | + $res = $sStore->getNotifyInSubquery($notify_id, $notify['sql'], $notify['hierarchy']);
|
| 1462 | +
|
| 1463 | + $no_matches = array_diff($res['monitoring'], $res['match']);
|
| 1464 | + $matches = array_diff($res['match'], $res['monitoring']);
|
| 1465 | + foreach($matches as $pid) {
|
| 1466 | + $pt = $sStore->getPageTitle($pid);
|
| 1467 | + if(!$pt) {
|
| 1468 | + continue;
|
| 1469 | + }
|
| 1470 | + $t = Title::makeTitle( NS_MAIN, $pt->page_title );
|
| 1471 | + $p_name = $t->getText().' ('.$t->getFullUrl().')';
|
| 1472 | + $p_html_name = '<a href="'.$t->getFullUrl().'">'.htmlspecialchars($t->getText()).'</a>';
|
| 1473 | +
|
| 1474 | + $this->m_userMsgs[$notify['user_id']] .= "\r\nSub page $page_name changed, $p_name matches NotifyMe '$notify[name]' now.";
|
| 1475 | + $hint = "Sub page $page_html_name changed, $p_html_name matches \"<b>".htmlspecialchars($notify[name])."</b>\" now.<br/>";
|
| 1476 | + $this->m_userHtmlNMMsgs[$notify['user_id']] .= $hint;
|
| 1477 | + $this->m_notifyHtmlMsgs[$notify_id] .= $hint;
|
| 1478 | + $this->m_newMonitor[] = array('notify_id'=>$notify_id, 'page_id'=>$pid);
|
| 1479 | +
|
| 1480 | + $this->m_userNMs[$notify['user_id']][] = $notify_id;
|
| 1481 | + }
|
| 1482 | + foreach($no_matches as $pid) {
|
| 1483 | + $pt = $sStore->getPageTitle($pid);
|
| 1484 | + if(!$pt) {
|
| 1485 | + continue;
|
| 1486 | + }
|
| 1487 | + $t = Title::makeTitle( NS_MAIN, $pt->page_title );
|
| 1488 | + $p_name = $t->getText().' ('.$t->getFullUrl().')';
|
| 1489 | + $p_html_name = '<a href="'.$t->getFullUrl().'">'.htmlspecialchars($t->getText()).'</a>';
|
| 1490 | +
|
| 1491 | + $this->m_userMsgs[$notify['user_id']] .= "\r\nSub page $page_name changed, page $p_name does not match NotifyMe '$notify[name]' now.";
|
| 1492 | + $hint = "Sub page $page_html_name changed, $p_html_name does not match \"<b>".htmlspecialchars($notify[name])."</b>\" now.<br/>";
|
| 1493 | + $this->m_userHtmlNMMsgs[$notify['user_id']] .= $hint;
|
| 1494 | + $this->m_notifyHtmlMsgs[$notify_id] .= $hint;
|
| 1495 | + $this->m_removeMonitored[] = array('notify_id'=>$notify_id, 'page_id'=>$pid);
|
| 1496 | +
|
| 1497 | + $this->m_userNMs[$notify['user_id']][] = $notify_id;
|
| 1498 | + }
|
| 1499 | + }
|
| 1500 | +
|
| 1501 | + $sStore->removeNotifyMonitor($this->m_removeMonitored);
|
| 1502 | + $sStore->addNotifyMonitor($this->m_newMonitor);
|
| 1503 | + }
|
| 1504 | + private function applyStyle($html) {
|
| 1505 | + $html = str_replace("class=\"smwtable\"", "style=\"background-color: #EEEEFF;\"", $html);
|
| 1506 | + $html = str_replace("<th", "<th style=\"background-color: #EEEEFF;text-align: left;\"", $html);
|
| 1507 | + $html = str_replace("<td", "<td style=\"background-color: #FFFFFF;padding: 1px;padding-left: 5px;padding-right: 5px;text-align: left;vertical-align: top;\"", $html);
|
| 1508 | + return $html;
|
| 1509 | + }
|
| 1510 | + public function notifyUsers() {
|
| 1511 | + global $wgSitename, $wgSMTP, $wgEmergencyContact, $wgEnotifyMeJob;
|
| 1512 | + $sStore = NMStorage::getDatabase();
|
| 1513 | +
|
| 1514 | + $nm_send_jobs = array();
|
| 1515 | + $id = 0;
|
| 1516 | +
|
| 1517 | + if(count($this->m_notifyHtmlMsgs) > 0) {
|
| 1518 | + $notifications = $sStore->getNotifyMe(array_keys($this->m_notifyHtmlMsgs));
|
| 1519 | + }
|
| 1520 | + $html_style = '';
|
| 1521 | +//<style>
|
| 1522 | +//table.smwtable{background-color: #EEEEFF;}
|
| 1523 | +//table.smwtable th{background-color: #EEEEFF;text-align: left;}
|
| 1524 | +//table.smwtable td{background-color: #FFFFFF;padding: 1px;padding-left: 5px;padding-right: 5px;text-align: left;vertical-align: top;}
|
| 1525 | +//table.smwtable tr.smwfooter td{font-size: 90%;line-height: 1;background-color: #EEEEFF;padding: 0px;padding-left: 5px;padding-right: 5px;text-align: right;vertical-align: top;}
|
| 1526 | +//</style>';
|
| 1527 | + $html_showall = array();
|
| 1528 | + foreach($this->m_notifyHtmlMsgs as $notify_id=>$msg) {
|
| 1529 | + $html_msg = $html_style;
|
| 1530 | + $showing_all = false;
|
| 1531 | + if(isset($notifications[$notify_id]) && $notifications[$notify_id]['show_all']) {
|
| 1532 | + SMWQueryProcessor::processFunctionParams(explode("\n", $notifications[$notify_id]['query']), $querystring, $params, $printouts);
|
| 1533 | +
|
| 1534 | + $format = 'auto';
|
| 1535 | + if (array_key_exists('format', $params)) {
|
| 1536 | + $format = strtolower(trim($params['format']));
|
| 1537 | + global $smwgResultFormats;
|
| 1538 | + if ( !array_key_exists($format, $smwgResultFormats) ) {
|
| 1539 | + $format = 'auto';
|
| 1540 | + }
|
| 1541 | + }
|
| 1542 | + $query = SMWQueryProcessor::createQuery($querystring, $params, SMWQueryProcessor::INLINE_QUERY, $format, $printouts);
|
| 1543 | + $res = smwfGetStore()->getQueryResult($query);
|
| 1544 | + $printer = SMWQueryProcessor::getResultPrinter($format, SMWQueryProcessor::INLINE_QUERY, $res);
|
| 1545 | + $result = $printer->getResult($res, $params, SMW_OUTPUT_HTML);
|
| 1546 | + $html_msg .= $result . '<br/>';
|
| 1547 | + $html_showall[$notify_id] = array ('name'=>$notifications[$notify_id]['name'], 'html'=>$result);
|
| 1548 | +
|
| 1549 | + $showing_all = true;
|
| 1550 | + $link = $res->getQueryLink()->getURL();
|
| 1551 | + }
|
| 1552 | + global $smwgNMHideDiffWhenShowAll;
|
| 1553 | + if(!($smwgNMHideDiffWhenShowAll && $showing_all)) {
|
| 1554 | + $html_msg .= '<p><b>Semantic changes from last revision:</b><br/><span style="font-size: 8pt;">'.$this->m_notifyHtmlMsgs[$notify_id].'</span></P>';
|
| 1555 | + if(isset($this->m_notifyHtmlPropMsgs[$notify_id])) {
|
| 1556 | + $html_msg .= "<P><table class=\"smwtable\"><tr><th>Semantic type</th><th>Name</th><th>Action</th><th>Deleted</th><th>Added</th></tr>";
|
| 1557 | + $html_msg .= $this->m_notifyHtmlPropMsgs[$notify_id];
|
| 1558 | + $html_msg .= "</table></P>";
|
| 1559 | + }
|
| 1560 | + }
|
| 1561 | + if($showing_all) {
|
| 1562 | + $id = $sStore->addNotifyRSS('nid', $notify_id, "All current items, ".date('Y-m-d H:i:s', time()), $this->applyStyle($html_msg), $link);
|
| 1563 | + } else {
|
| 1564 | + $id = $sStore->addNotifyRSS('nid', $notify_id, $this->m_title->getText(), $this->applyStyle($html_msg));
|
| 1565 | + }
|
| 1566 | + }
|
| 1567 | + foreach($this->m_userMsgs as $user_id=>$msg) {
|
| 1568 | + // generate RSS items
|
| 1569 | + $html_msg = $html_style;
|
| 1570 | + foreach(array_unique($this->m_userNMs[$user_id]) as $showall_nid) {
|
| 1571 | + if(isset($html_showall[$showall_nid])) {
|
| 1572 | + $html_msg .= "<br/>All current items for \"<b>".$html_showall[$showall_nid]['name']."</b>\":<br/>";
|
| 1573 | + $html_msg .= $html_showall[$showall_nid]['html'] . '<br/>';
|
| 1574 | + }
|
| 1575 | + }
|
| 1576 | +
|
| 1577 | + $html_msg .= '<p><b>Semantic changes from last revision:</b><br/><span style="font-size: 8pt;">'.$this->m_userHtmlNMMsgs[$user_id].'</span></P>';
|
| 1578 | + if(isset($this->m_userHtmlPropMsgs[$user_id])) {
|
| 1579 | + $html_msg .= "<P><table class=\"smwtable\"><tr><th>Semantic type</th><th>Name</th><th>Action</th><th>Deleted</th><th>Added</th></tr>";
|
| 1580 | + $html_msg .= $this->m_userHtmlPropMsgs[$user_id];
|
| 1581 | + $html_msg .= "</table></P>";
|
| 1582 | + }
|
| 1583 | +
|
| 1584 | + $id = $sStore->addNotifyRSS('uid', $user_id, $this->m_title->getText(), $this->applyStyle($html_msg));
|
| 1585 | +
|
| 1586 | + if($wgEnotifyMeJob) {
|
| 1587 | + // send notifications by mail
|
| 1588 | + $user_info = $sStore->getUserInfo($user_id);
|
| 1589 | + if(($user_info->user_email != '') && $this->getUserNMOption($user_info->user_options)) {
|
| 1590 | + $name = (($user_info->user_real_name=='')?$user_info->user_name:$user_info->user_real_name);
|
| 1591 | + $body = "Dear Mr./Mrs. $name,\r\n$msg".
|
| 1592 | + "\r\n\r\nSincerely yours,\r\nSMW NotifyMe Bot";
|
| 1593 | +
|
| 1594 | + $params = array('to' => new MailAddress($user_info->user_email, $name),
|
| 1595 | + 'from' => new MailAddress($wgEmergencyContact, 'Admin'),
|
| 1596 | + 'subj' => 'New SMW Notification comes, from '.$wgSitename,
|
| 1597 | + 'body' => $body,
|
| 1598 | + 'replyto' => new MailAddress($wgEmergencyContact, 'Admin'));
|
| 1599 | +
|
| 1600 | + $nm_send_jobs[] = new SMW_NMSendMailJob($this->m_title, $params);
|
| 1601 | + }
|
| 1602 | + }
|
| 1603 | + }
|
| 1604 | +
|
| 1605 | + if( $wgEnotifyMeJob ) {
|
| 1606 | + if( count( $nm_send_jobs ) ) {
|
| 1607 | + Job :: batchInsert($nm_send_jobs);
|
| 1608 | + }
|
| 1609 | + } else {
|
| 1610 | + global $phpInterpreter;
|
| 1611 | + if (!isset($phpInterpreter)) {
|
| 1612 | + // if $phpInterpreter is not set, assume it is in search path
|
| 1613 | + // if not, starting of bot will FAIL!
|
| 1614 | + $phpInterpreter = "php";
|
| 1615 | + }
|
| 1616 | + // copy from SMW_GardeningBot.php
|
| 1617 | + ob_start();
|
| 1618 | + phpinfo();
|
| 1619 | + $info = ob_get_contents();
|
| 1620 | + ob_end_clean();
|
| 1621 | + //Get Systemstring
|
| 1622 | + preg_match('!\nSystem(.*?)\n!is',strip_tags($info),$ma);
|
| 1623 | + //Check if it consists 'windows' as string
|
| 1624 | + preg_match('/[Ww]indows/',$ma[1],$os);
|
| 1625 | + global $smwgNMIP ;
|
| 1626 | + if($os[0]=='' && $os[0]==null ) {
|
| 1627 | +
|
| 1628 | + //FIXME: $runCommand must allow whitespaces in paths too
|
| 1629 | + $runCommand = "$phpInterpreter -q $smwgNMIP/specials/SMWNotifyMe/SMW_NMSendMailAsync.php";
|
| 1630 | + //TODO: test async code for linux.
|
| 1631 | + //low prio
|
| 1632 | + $nullResult = `$runCommand > /dev/null &`;
|
| 1633 | + }
|
| 1634 | + else //windowze
|
| 1635 | + {
|
| 1636 | + $runCommand = "\"\"$phpInterpreter\" -q \"$smwgNMIP/specials/SMWNotifyMe/SMW_NMSendMailAsync.php\"\"";
|
| 1637 | + $wshShell = new COM("WScript.Shell");
|
| 1638 | + $runCommand = "cmd /C ".$runCommand;
|
| 1639 | +
|
| 1640 | + $oExec = $wshShell->Run($runCommand, 7, false);
|
| 1641 | + }
|
| 1642 | + }
|
| 1643 | + }
|
| 1644 | + // copy from user class
|
| 1645 | + function getUserNMOption( $str ) {
|
| 1646 | + $options = array();
|
| 1647 | + $a = explode( "\n", $str );
|
| 1648 | + foreach ( $a as $s ) {
|
| 1649 | + $m = array();
|
| 1650 | + if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
|
| 1651 | + $options[$m[1]] = $m[2];
|
| 1652 | + }
|
| 1653 | + }
|
| 1654 | + return $options['enotifyme'];
|
| 1655 | + }
|
| 1656 | +}
|
Index: trunk/extensions/SemanticNotifyMe/includes/SNM_Initialize.php |
— | — | @@ -0,0 +1,294 @@ |
| 2 | +<?php
|
| 3 | +/*
|
| 4 | + * Created on 24.6.2009
|
| 5 | + *
|
| 6 | + * Author: dch
|
| 7 | + */
|
| 8 | +if ( !defined( 'MEDIAWIKI' ) ) die;
|
| 9 | +
|
| 10 | +define('SMW_NM_VERSION', '0.5');
|
| 11 | +
|
| 12 | +$smwgNMIP = $IP . '/extensions/SemanticNotifyMe';
|
| 13 | +$smwgNMScriptPath = $wgScriptPath . '/extensions/SemanticNotifyMe';
|
| 14 | +
|
| 15 | +global $wgExtensionFunctions;
|
| 16 | +$wgExtensionFunctions[] = 'smwgNMSetupExtension';
|
| 17 | +
|
| 18 | +global $wgDefaultUserOptions;
|
| 19 | +$wgDefaultUserOptions['enotifyme'] = 1;
|
| 20 | +
|
| 21 | +function smwfNMInitMessages() {
|
| 22 | + global $smwgNMMessagesInitialized;
|
| 23 | + if (isset($smwgNMMessagesInitialized)) return; // prevent double init
|
| 24 | +
|
| 25 | + smwfNMInitUserMessages(); // lazy init for ajax calls
|
| 26 | +
|
| 27 | + $smwgNMMessagesInitialized = true;
|
| 28 | +}
|
| 29 | +function smwfNMInitUserMessages() {
|
| 30 | + global $wgMessageCache, $smwgNMContLang, $wgLanguageCode;
|
| 31 | + smwfNMInitContentLanguage($wgLanguageCode);
|
| 32 | +
|
| 33 | + global $smwgNMIP, $smwgNMLang;
|
| 34 | + if (!empty($smwgNMLang)) { return; }
|
| 35 | + global $wgMessageCache, $wgLang;
|
| 36 | + $smwLangClass = 'SMW_NMLanguage' . str_replace( '-', '_', ucfirst( $wgLang->getCode() ) );
|
| 37 | +
|
| 38 | + if (file_exists($smwgNMIP . '/languages/'. $smwLangClass . '.php')) {
|
| 39 | + include_once( $smwgNMIP . '/languages/'. $smwLangClass . '.php' );
|
| 40 | + }
|
| 41 | + // fallback if language not supported
|
| 42 | + if ( !class_exists($smwLangClass)) {
|
| 43 | + global $smwgNMContLang;
|
| 44 | + $smwgNMLang = $smwgNMContLang;
|
| 45 | + } else {
|
| 46 | + $smwgNMLang = new $smwLangClass();
|
| 47 | + }
|
| 48 | +
|
| 49 | + $wgMessageCache->addMessages($smwgNMLang->getUserMsgArray(), $wgLang->getCode());
|
| 50 | +}
|
| 51 | +function smwfNMInitContentLanguage($langcode) {
|
| 52 | + global $smwgNMIP, $smwgNMContLang;
|
| 53 | + if (!empty($smwgNMContLang)) { return; }
|
| 54 | +
|
| 55 | + $smwContLangClass = 'SMW_NMLanguage' . str_replace( '-', '_', ucfirst( $langcode ) );
|
| 56 | +
|
| 57 | + if (file_exists($smwgNMIP . '/languages/'. $smwContLangClass . '.php')) {
|
| 58 | + include_once( $smwgNMIP . '/languages/'. $smwContLangClass . '.php' );
|
| 59 | + }
|
| 60 | +
|
| 61 | + // fallback if language not supported
|
| 62 | + if ( !class_exists($smwContLangClass)) {
|
| 63 | + include_once($smwgNMIP . '/languages/SMW_NMLanguageEn.php');
|
| 64 | + $smwContLangClass = 'SMW_NMLanguageEn';
|
| 65 | + }
|
| 66 | + $smwgNMContLang = new $smwContLangClass();
|
| 67 | +}
|
| 68 | +
|
| 69 | +function smwfNMInitializeTables() {
|
| 70 | + global $smwgNMIP;
|
| 71 | + require_once( $smwgNMIP . '/includes/SMW_NMStorage.php' );
|
| 72 | + NMStorage::getDatabase()->setup(true);
|
| 73 | +
|
| 74 | + return true;
|
| 75 | +}
|
| 76 | +
|
| 77 | +function smwfNMGetAjaxMethodPrefix() {
|
| 78 | + $func_name = isset( $_POST["rs"] ) ? $_POST["rs"] : (isset( $_GET["rs"] ) ? $_GET["rs"] : NULL);
|
| 79 | + if ($func_name == NULL) return NULL;
|
| 80 | + return substr($func_name, 4, 4); // return _xx_ of smwf_xx_methodname, may return FALSE
|
| 81 | +}
|
| 82 | +
|
| 83 | +/**
|
| 84 | + * Intializes Semantic NotifyMe Extension.
|
| 85 | + * Called from SNM during initialization.
|
| 86 | + */
|
| 87 | +function smwgNMSetupExtension() {
|
| 88 | + global $smwgNMIP, $wgHooks, $wgJobClasses, $wgExtensionCredits;
|
| 89 | + global $wgAutoloadClasses, $wgSpecialPages, $wgSpecialPageGroups;
|
| 90 | +
|
| 91 | + smwfNMInitMessages();
|
| 92 | +
|
| 93 | +
|
| 94 | + // register SMW hooks
|
| 95 | + $wgHooks['smwInitializeTables'][] = 'smwfNMInitializeTables';
|
| 96 | +
|
| 97 | + $wgHooks['ArticleSaveComplete'][] = 'smwfNMSaveHook';
|
| 98 | + $wgHooks['ArticleUndelete'][] = 'smwfNMUndeleteHook';
|
| 99 | + $wgHooks['ArticleSave'][] = 'smwfNMPreSaveHook';
|
| 100 | + $wgHooks['ArticleDelete'][] = 'smwfNMPreDeleteHook';
|
| 101 | +
|
| 102 | + // queryinterface hook
|
| 103 | + $wgHooks['QI_AddButtons'][] = 'smwfNMAddQIButton';
|
| 104 | +
|
| 105 | + global $wgRequest, $wgContLang;
|
| 106 | +
|
| 107 | + $spns_text = $wgContLang->getNsText(NS_SPECIAL);
|
| 108 | +
|
| 109 | + // register AddHTMLHeader functions for special pages
|
| 110 | + // to include javascript and css files (only on special page requests).
|
| 111 | + if (stripos($wgRequest->getRequestURL(), $spns_text.":") !== false
|
| 112 | + || stripos($wgRequest->getRequestURL(), $spns_text."%3A") !== false) {
|
| 113 | +
|
| 114 | + if ( defined( 'SMW_HALO_VERSION' ) ){
|
| 115 | + // insert NM header hook before add halo header
|
| 116 | + foreach($wgHooks['BeforePageDisplay'] as $k=>$hookVal) {
|
| 117 | + if($hookVal=='smwfHaloAddHTMLHeader') {
|
| 118 | + $wgHooks['BeforePageDisplay'][$k] = 'smwNMAddHTMLHeader';
|
| 119 | + break;
|
| 120 | + }
|
| 121 | + }
|
| 122 | + $wgHooks['BeforePageDisplay'][]='smwfHaloAddHTMLHeader';
|
| 123 | + } else {
|
| 124 | + $wgHooks['BeforePageDisplay'][]='smwNMAddHTMLHeader';
|
| 125 | + }
|
| 126 | +
|
| 127 | + }
|
| 128 | +
|
| 129 | + $wgJobClasses['SMW_NMSendMailJob'] = 'SMW_NMSendMailJob';
|
| 130 | + require_once($smwgNMIP . '/includes/jobs/SMW_NMSendMailJob.php');
|
| 131 | + $wgJobClasses['SMWNMRefreshJob'] = 'SMWNMRefreshJob';
|
| 132 | + require_once($smwgNMIP . '/includes/jobs/SMW_NMRefreshJob.php');
|
| 133 | +
|
| 134 | + $wgAutoloadClasses['SMWNotifyProcessor'] = $smwgNMIP . '/includes/SMW_NotifyProcessor.php';
|
| 135 | +
|
| 136 | + $action = $wgRequest->getVal('action');
|
| 137 | + // add some AJAX calls
|
| 138 | + if ($action == 'ajax') {
|
| 139 | + $method_prefix = smwfNMGetAjaxMethodPrefix();
|
| 140 | +
|
| 141 | + // decide according to ajax method prefix which script(s) to import
|
| 142 | + switch($method_prefix) {
|
| 143 | + case '_nm_' :
|
| 144 | + require_once($smwgNMIP . '/specials/SMWNotifyMe/SMW_NotAjaxAccess.php');
|
| 145 | + break;
|
| 146 | + }
|
| 147 | + } else { // otherwise register special pages
|
| 148 | + $wgAutoloadClasses['SMWNotifyMe'] = $smwgNMIP . '/specials/SMWNotifyMe/SMWNotifyMe.php';
|
| 149 | + $wgSpecialPages['NotifyMe'] = array('SMWNotifyMe');
|
| 150 | + $wgSpecialPageGroups['NotifyMe'] = 'smwplus_group';
|
| 151 | + }
|
| 152 | +
|
| 153 | + // Register Credits
|
| 154 | + $wgExtensionCredits['parserhook'][]= array(
|
| 155 | + 'name'=>'Semantic NotifyMe Extension', 'version'=>SMW_NM_VERSION,
|
| 156 | + 'author'=>"Ning Hu, Justin Zhang, [http://smwforum.ontoprise.com/smwforum/index.php/Jesse_Wang Jesse Wang], sponsored by [http://projecthalo.com Project Halo], [http://www.vulcan.com Vulcan Inc.]",
|
| 157 | + 'url'=>'http://wiking.vulcan.com/dev',
|
| 158 | + 'description' => 'Notify wiki user with specified queries.');
|
| 159 | +
|
| 160 | + return true;
|
| 161 | +}
|
| 162 | +
|
| 163 | +function smwfNMGetJSLanguageScripts(&$pathlng, &$userpathlng) {
|
| 164 | + global $smwgNMIP, $wgLanguageCode, $smwgNMScriptPath, $wgUser;
|
| 165 | +
|
| 166 | + // content language file
|
| 167 | + $lng = '/scripts/Language/SMW_NMLanguage';
|
| 168 | + if (!empty($wgLanguageCode)) {
|
| 169 | + $lng .= ucfirst($wgLanguageCode).'.js';
|
| 170 | + if (file_exists($smwgNMIP . $lng)) {
|
| 171 | + $pathlng = $smwgNMScriptPath . $lng;
|
| 172 | + } else {
|
| 173 | + $pathlng = $smwgNMScriptPath . '/scripts/Language/SMW_NMLanguageEn.js';
|
| 174 | + }
|
| 175 | + } else {
|
| 176 | + $pathlng = $smwgNMScriptPath . '/scripts/Language/SMW_NMLanguageEn.js';
|
| 177 | + }
|
| 178 | +
|
| 179 | + // user language file
|
| 180 | + $lng = '/scripts/Language/SMW_NMLanguage';
|
| 181 | + if (isset($wgUser)) {
|
| 182 | + $lng .= "User".ucfirst($wgUser->getOption('language')).'.js';
|
| 183 | + if (file_exists($smwgNMIP . $lng)) {
|
| 184 | + $userpathlng = $smwgNMScriptPath . $lng;
|
| 185 | + } else {
|
| 186 | + $userpathlng = $smwgNMScriptPath . '/scripts/Language/SMW_NMLanguageUserEn.js';
|
| 187 | + }
|
| 188 | + } else {
|
| 189 | + $userpathlng = $smwgNMScriptPath . '/scripts/Language/SMW_NMLanguageUserEn.js';
|
| 190 | + }
|
| 191 | +}
|
| 192 | +
|
| 193 | +// NotifyMe scripts callback
|
| 194 | +// includes necessary script and css files.
|
| 195 | +function smwNMAddHTMLHeader(&$out){
|
| 196 | + global $wgTitle;
|
| 197 | + if ($wgTitle->getNamespace() != NS_SPECIAL) return true;
|
| 198 | +
|
| 199 | + global $smwgNMScriptPath, $smwgScriptPath;
|
| 200 | + smwfNMGetJSLanguageScripts($pathlng, $userpathlng);
|
| 201 | +
|
| 202 | + if ( defined( 'SMW_HALO_VERSION' ) ){
|
| 203 | + global $smwgHaloScriptPath, $smwgDeployVersion;
|
| 204 | +
|
| 205 | + $jsm = SMWResourceManager::SINGLETON();
|
| 206 | +
|
| 207 | + $jsm->addScriptIf($smwgHaloScriptPath . '/scripts/prototype.js', "all", -1, NS_SPECIAL.":NotifyMe");
|
| 208 | + $jsm->addScriptIf($smwgHaloScriptPath . '/scripts/Logger/smw_logger.js', "all", -1, NS_SPECIAL.":NotifyMe");
|
| 209 | + $jsm->addScriptIf($smwgScriptPath . '/skins/SMW_tooltip.js', "all", -1, NS_SPECIAL.":NotifyMe");
|
| 210 | +
|
| 211 | + $jsm->addScriptIf($smwgNMScriptPath . '/scripts/Language/SMW_NMLanguage.js', "all", -1, NS_SPECIAL.":NotifyMe");
|
| 212 | + $jsm->addScriptIf($pathlng, "all", -1, NS_SPECIAL.":NotifyMe");
|
| 213 | + $jsm->addScriptIf($userpathlng, "all", -1, NS_SPECIAL.":NotifyMe");
|
| 214 | + $jsm->addScriptIf($smwgNMScriptPath . '/scripts/NotifyMe/NotifyHelper.js', "all", -1, NS_SPECIAL.":NotifyMe");
|
| 215 | +
|
| 216 | + $jsm->addScriptIf($smwgNMScriptPath . '/scripts/Language/SMW_NMLanguage.js', "all", -1, NS_SPECIAL.":QueryInterface");
|
| 217 | + $jsm->addScriptIf($pathlng, "all", -1, NS_SPECIAL.":QueryInterface");
|
| 218 | + $jsm->addScriptIf($userpathlng, "all", -1, NS_SPECIAL.":QueryInterface");
|
| 219 | + $jsm->addScriptIf($smwgNMScriptPath . '/scripts/NotifyMe/NotifyHelper.js', "all", -1, NS_SPECIAL.":QueryInterface");
|
| 220 | +
|
| 221 | + $jsm->addCSSIf($smwgScriptPath . '/skins/SMW_custom.css', "all", -1, NS_SPECIAL.":NotifyMe");
|
| 222 | + $jsm->addCSSIf($smwgNMScriptPath . '/skins/nm.css', "all", -1, NS_SPECIAL.":NotifyMe");
|
| 223 | + } else {
|
| 224 | + global $wgRequest;
|
| 225 | + $scripts = array();
|
| 226 | + $$css = array();
|
| 227 | +
|
| 228 | + // read state
|
| 229 | + if ($wgRequest != NULL && $wgTitle != NULL) {
|
| 230 | + $action = $wgRequest->getVal("action");
|
| 231 | + // $action of NULL or '' means view mode
|
| 232 | + $action = $action == NULL || $action == '' ? "view" : $action;
|
| 233 | + $namespace = $wgTitle->getNamespace();
|
| 234 | + $page = $wgTitle->getNamespace().":".$wgTitle->getText();
|
| 235 | +
|
| 236 | + } else { // if no state could be read, set default -> load all!
|
| 237 | + $action = "all";
|
| 238 | + $namespace = -1;
|
| 239 | + $page = array();
|
| 240 | + }
|
| 241 | + if(($namespace == NS_SPECIAL || $namespace == -1) && ($page == NS_SPECIAL.":NotifyMe")) {
|
| 242 | + $out->addScript('<script type="text/javascript" src="'. $smwgNMScriptPath . '/scripts/prototype.js"></script>');
|
| 243 | + $out->addScript('<script type="text/javascript" src="'. $smwgScriptPath . '/skins/SMW_tooltip.js"></script>');
|
| 244 | + $out->addScript('<script type="text/javascript" src="'. $smwgNMScriptPath . '/scripts/Language/SMW_NMLanguage.js"></script>');
|
| 245 | + $out->addScript('<script type="text/javascript" src="'. $pathlng .'"></script>');
|
| 246 | + $out->addScript('<script type="text/javascript" src="'. $userpathlng .'"></script>');
|
| 247 | + $out->addScript('<script type="text/javascript" src="'. $smwgNMScriptPath . '/scripts/NotifyMe/NotifyHelper.js"></script>');
|
| 248 | +
|
| 249 | + $out->addLink(array(
|
| 250 | + 'rel' => 'stylesheet',
|
| 251 | + 'type' => 'text/css',
|
| 252 | + 'media' => 'screen, projection',
|
| 253 | + 'href' => $smwgScriptPath . '/skins/SMW_custom.css'
|
| 254 | + ));
|
| 255 | + $out->addLink(array(
|
| 256 | + 'rel' => 'stylesheet',
|
| 257 | + 'type' => 'text/css',
|
| 258 | + 'media' => 'screen, projection',
|
| 259 | + 'href' => $smwgNMScriptPath . '/skins/nm.css'
|
| 260 | + ));
|
| 261 | + }
|
| 262 | + }
|
| 263 | + return true; // do not load other scripts or CSS
|
| 264 | +}
|
| 265 | +
|
| 266 | +
|
| 267 | +function smwfNMPreSaveHook(&$article, &$user, &$text, &$summary, $minor, $watch, $sectionanchor, &$flags) {
|
| 268 | + SMWNotifyProcessor::prepareArticleSave($article->getTitle());
|
| 269 | +
|
| 270 | + return true;
|
| 271 | +}
|
| 272 | +function smwfNMPreDeleteHook(&$article, &$user, &$reason) {
|
| 273 | + SMWNotifyProcessor::articleDelete($article->getTitle(), $reason);
|
| 274 | +
|
| 275 | + return true;
|
| 276 | +}
|
| 277 | +function smwfNMSaveHook(&$article, &$user, &$text) {
|
| 278 | + SMWNotifyProcessor::articleSavedComplete($article->getTitle());
|
| 279 | +
|
| 280 | + return true; // always return true, in order not to stop MW's hook processing!
|
| 281 | +}
|
| 282 | +function smwfNMUndeleteHook(&$title, $create) {
|
| 283 | + SMWNotifyProcessor::articleSavedComplete($title);
|
| 284 | + return true; // always return true, in order not to stop MW's hook processing!
|
| 285 | +}
|
| 286 | +function smwfNMAddQIButton(&$buttons) {
|
| 287 | + global $wgUser;
|
| 288 | + $user_id = $wgUser->getId();
|
| 289 | + $buttons = '';
|
| 290 | + if($user_id != 0) {
|
| 291 | + $buttons = '<button class="btn" onclick="notifyhelper.saveQueryToNotify()" onmouseover="this.className=\'btn btnhov\'; Tip(\'' . wfMsg('smw_qi_tt_addNotify') . '\')" onmouseout="this.className=\'btn\'">' . wfMsg('smw_qi_addNotify') . '</button>';
|
| 292 | + }
|
| 293 | + return true;
|
| 294 | +}
|
| 295 | +?> |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/includes/storage/SMW_SQLStore2_QueriesNM.php |
— | — | @@ -0,0 +1,724 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Query answering functions for SMWSQLStore2. Separated frmo main code for readability and |
| 5 | + * for avoiding twice the amount of code being required on every use of a simple storage function.
|
| 6 | + *
|
| 7 | + * based on SMW, SMW_SQLStore2_Queries.php, apply query parser to NotifyMe |
| 8 | + * |
| 9 | + * @author dch |
| 10 | + */ |
| 11 | +
|
| 12 | +global $smwgIP;
|
| 13 | +include_once("$smwgIP/includes/storage/SMW_SQLStore2_Queries.php");
|
| 14 | + |
| 15 | +/** |
| 16 | + * Class that implements query answering for SMWSQLStore2. |
| 17 | + */ |
| 18 | +class SMWSQLStore2QueryEngineNM { |
| 19 | + |
| 20 | + /// Database slave to be used |
| 21 | + protected $m_dbs; /// TODO: should temporary tables be created on the master DB? |
| 22 | + /// Parent SMWSQLStore2 |
| 23 | + protected $m_store; |
| 24 | + /// Query mode copied from given query, some submethods act differently when in SMWQuery::MODE_DEBUG |
| 25 | + protected $m_qmode; |
| 26 | + /// Array of generated query descriptions |
| 27 | + protected $m_queries = array(); |
| 28 | + /// Array of arrays of executed queries, indexed by the temporary table names results were fed into |
| 29 | + protected $m_querylog = array(); |
| 30 | + /** |
| 31 | + * Array of sorting requests ("Property_name" => "ASC"/"DESC". Used during query |
| 32 | + * processing (where these property names are searched while compiling the query |
| 33 | + * conditions). |
| 34 | + */ |
| 35 | + protected $m_sortkeys; |
| 36 | + /// Cache of computed hierarchy queries for reuse, cat/prop-value string => tablename |
| 37 | + protected $m_hierarchies = array(); |
| 38 | + /// Local collection of error strings, passed on to callers if possible. |
| 39 | + protected $m_errors = array(); |
| 40 | + |
| 41 | + public function __construct(&$parentstore, &$dbslave) { |
| 42 | + $this->m_store = $parentstore; |
| 43 | + $this->m_dbs = $dbslave; |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * Refresh the concept cache for the given concept. |
| 48 | + * |
| 49 | + * @param $concept Title |
| 50 | + */ |
| 51 | + public function refreshConceptCache($concept) { |
| 52 | + global $smwgQMaxLimit, $smwgQConceptFeatures; |
| 53 | + $cid = $this->m_store->getSMWPageID($concept->getDBKey(), SMW_NS_CONCEPT, ''); |
| 54 | + $cid_c = $this->m_store->getSMWPageID($concept->getDBKey(), SMW_NS_CONCEPT, '', false); |
| 55 | + if ($cid != $cid_c) { |
| 56 | + $this->m_errors[] = "Skipping redirect concept."; |
| 57 | + return $this->m_errors; |
| 58 | + } |
| 59 | + $dv = end($this->m_store->getPropertyValues($concept, SMWPropertyValue::makeProperty('_CONC'))); |
| 60 | + $desctxt = ($dv!==false)?$dv->getWikiValue():false; |
| 61 | + $this->m_errors = array(); |
| 62 | + if ($desctxt) { // concept found |
| 63 | + $this->m_qmode = SMWQuery::MODE_INSTANCES; |
| 64 | + $this->m_queries = array(); |
| 65 | + $this->m_hierarchies = array(); |
| 66 | + $this->m_querylog = array(); |
| 67 | + $this->m_sortkeys = array(); |
| 68 | + SMWSQLStore2Query::$qnum = 0; |
| 69 | + |
| 70 | + // Pre-process query: |
| 71 | + $qp = new SMWQueryParser($smwgQConceptFeatures); |
| 72 | + $desc = $qp->getQueryDescription($desctxt); |
| 73 | + $qid = $this->compileQueries($desc); |
| 74 | + $this->executeQueries($this->m_queries[$qid]); // execute query tree, resolve all dependencies |
| 75 | + $qobj = $this->m_queries[$qid]; |
| 76 | + if ($qobj->joinfield === '') { |
| 77 | + return; |
| 78 | + } |
| 79 | + // Update database: |
| 80 | + $this->m_dbs->delete('smw_conccache', array('o_id' => $cid), 'SMW::refreshConceptCache'); |
| 81 | + $this->m_dbs->query("INSERT IGNORE INTO " . $this->m_dbs->tableName('smw_conccache') . |
| 82 | + " SELECT DISTINCT $qobj->joinfield AS s_id, $cid AS o_id FROM " . |
| 83 | + $this->m_dbs->tableName($qobj->jointable) . " AS $qobj->alias" . $qobj->from . |
| 84 | + ($qobj->where?" WHERE ":'') . $qobj->where . " LIMIT $smwgQMaxLimit", |
| 85 | + 'SMW::refreshConceptCache'); |
| 86 | + $this->m_dbs->update('smw_conc2', array('cache_date' => strtotime("now"), 'cache_count' => $this->m_dbs->affectedRows()), array('s_id' => $cid), 'SMW::refreshConceptCache'); |
| 87 | + } else { // just delete old data if there is any |
| 88 | + $this->m_dbs->delete('smw_conccache', array('o_id' => $cid), 'SMW::refreshConceptCache'); |
| 89 | + $this->m_dbs->update('smw_conc2', array('cache_date' => NULL, 'cache_count' => NULL), array('s_id' => $cid), 'SMW::refreshConceptCache'); |
| 90 | + $this->m_errors[] = "No concept description found."; |
| 91 | + } |
| 92 | + $this->cleanUp(); |
| 93 | + return $this->m_errors; |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Delete the concept cache for the given concept. |
| 98 | + * |
| 99 | + * @param $concept Title |
| 100 | + */ |
| 101 | + public function deleteConceptCache($concept) { |
| 102 | + $cid = $this->m_store->getSMWPageID($concept->getDBKey(), SMW_NS_CONCEPT, '', false); |
| 103 | + $this->m_dbs->delete('smw_conccache', array('o_id' => $cid), 'SMW::refreshConceptCache'); |
| 104 | + $this->m_dbs->update('smw_conc2', array('cache_date' => NULL, 'cache_count' => NULL), array('s_id' => $cid), 'SMW::refreshConceptCache'); |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * The new SQL store's implementation of query answering. |
| 109 | + */ |
| 110 | + public function getQueryResult(SMWQuery $query) { |
| 111 | + if ($query->querymode == SMWQuery::MODE_NONE) { // don't query, but return something to printer |
| 112 | + $result = new SMWQueryResult($query->getDescription()->getPrintrequests(), $query, true); |
| 113 | + return $result; |
| 114 | + } |
| 115 | + $this->m_qmode = $query->querymode; |
| 116 | + $this->m_queries = array(); |
| 117 | + $this->m_hierarchies = array(); |
| 118 | + $this->m_querylog = array(); |
| 119 | + $this->m_errors = array(); |
| 120 | + SMWSQLStore2Query::$qnum = 0; |
| 121 | + $this->m_sortkeys = $query->sortkeys; |
| 122 | + // manually make final root query (to retrieve namespace,title): |
| 123 | + $rootid = SMWSQLStore2Query::$qnum; |
| 124 | + $qobj = new SMWSQLStore2Query(); |
| 125 | + $qobj->jointable = 'smw_ids'; |
| 126 | + $qobj->joinfield = "$qobj->alias.smw_id"; |
| 127 | + // build query dependency tree: |
| 128 | + wfProfileIn('SMWSQLStore2Queries::compileMainQuery (SMW)'); |
| 129 | + $qid = $this->compileQueries($query->getDescription()); // compile query, build query "plan" |
| 130 | + wfProfileOut('SMWSQLStore2Queries::compileMainQuery (SMW)'); |
| 131 | + if ($qid < 0) { // no valid/supported condition; ensure that at least only proper pages are delivered |
| 132 | + $qid = SMWSQLStore2Query::$qnum; |
| 133 | + $q = new SMWSQLStore2Query(); |
| 134 | + $q->jointable = 'smw_ids'; |
| 135 | + $q->joinfield = "$q->alias.smw_id"; |
| 136 | + $q->where = "$q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWIW) . " AND $q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWREDIIW) . " AND $q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWBORDERIW) . " AND $q->alias.smw_iw!=" . $this->m_dbs->addQuotes(SMW_SQL2_SMWINTDEFIW); |
| 137 | + $this->m_queries[$qid] = $q; |
| 138 | + } |
| 139 | + // append query to root: |
| 140 | + $qobj->components = array($qid => "$qobj->alias.smw_id"); |
| 141 | + $qobj->sortfields = $this->m_queries[$qid]->sortfields; |
| 142 | + $this->m_queries[$rootid] = $qobj; |
| 143 | + |
| 144 | + $this->applyOrderConditions($query,$rootid); // may extend query if needed for sorting |
| 145 | + wfProfileIn('SMWSQLStore2Queries::executeMainQuery (SMW)'); |
| 146 | + $this->executeQueries($this->m_queries[$rootid]); // execute query tree, resolve all dependencies |
| 147 | + wfProfileOut('SMWSQLStore2Queries::executeMainQuery (SMW)'); |
| 148 | + $result = $this->getNMQueryResult($query,$rootid);
|
| 149 | +
|
| 150 | + $this->cleanUp(); |
| 151 | + $query->addErrors($this->m_errors); |
| 152 | + return $result; |
| 153 | + } |
| 154 | +
|
| 155 | + // added by dch
|
| 156 | + protected function getNMQueryResult($query,$rootid) {
|
| 157 | + wfProfileIn('SMWSQLStore2Queries::getNMQueryResult (SMW)');
|
| 158 | + $qobj = $this->m_queries[$rootid];
|
| 159 | + if ($qobj->joinfield !== '') {
|
| 160 | + $sql = "SELECT DISTINCT $qobj->alias.smw_title AS t,$qobj->alias.smw_namespace AS ns FROM " .
|
| 161 | + $this->m_dbs->tableName($qobj->jointable) . " AS $qobj->alias" . $qobj->from .
|
| 162 | + (($qobj->where=='')?'':' WHERE ') . $qobj->where;
|
| 163 | + } else { // empty result, no query needed
|
| 164 | + wfProfileOut('SMWSQLStore2Queries::getNMQueryResult (SMW)');
|
| 165 | + return false;
|
| 166 | + }
|
| 167 | + $tmp = '';
|
| 168 | + foreach ($this->m_querylog as $table => $log) {
|
| 169 | + foreach ($log as $l => $v) {
|
| 170 | + $tmp .= ($tmp!=''?';':'')."$table:$l";
|
| 171 | + }
|
| 172 | + }
|
| 173 | + $result = array('sql'=>$sql, 'tmp_hierarchy'=>$tmp, 'page_ids'=>array());
|
| 174 | +
|
| 175 | + $res = $this->m_dbs->select($this->m_dbs->tableName($qobj->jointable) . " AS $qobj->alias" . $qobj->from, "DISTINCT $qobj->alias.smw_title AS t,$qobj->alias.smw_namespace AS ns", $qobj->where, 'SMW::getQueryResult');
|
| 176 | + while ( $row = $this->m_dbs->fetchObject($res) ) {
|
| 177 | + if($row->ns != NS_MAIN) continue;
|
| 178 | +
|
| 179 | + $title = Title::makeTitle($row->ns, $row->t);
|
| 180 | + $result['page_ids'][] = $title->getArticleID();
|
| 181 | + }
|
| 182 | + $this->m_dbs->freeResult($res);
|
| 183 | +
|
| 184 | + wfProfileOut('SMWSQLStore2Queries::getNMQueryResult (SMW)');
|
| 185 | + return $result;
|
| 186 | + }
|
| 187 | + |
| 188 | + /** |
| 189 | + * Create a new SMWSQLStore2Query object that can be used to obtain results for |
| 190 | + * the given description. The result is stored in $this->m_queries using a numeric |
| 191 | + * key that is returned as a result of the function. Returns -1 if no query was |
| 192 | + * created. |
| 193 | + */ |
| 194 | + protected function compileQueries(SMWDescription $description) { |
| 195 | + $qid = SMWSQLStore2Query::$qnum; |
| 196 | + $query = new SMWSQLStore2Query(); |
| 197 | + if ($description instanceof SMWSomeProperty) { |
| 198 | + $this->compilePropertyCondition($query, $description->getProperty(), $description->getDescription()); |
| 199 | + if ($query->type == SMW_SQL2_NOQUERY) $qid = -1; // drop that right away |
| 200 | + } elseif ($description instanceof SMWNamespaceDescription) { /// TODO: One instance of smw_ids on s_id always suffices (swm_id is KEY)! Doable in execution ... (PERFORMANCE) |
| 201 | + $query->jointable = 'smw_ids'; |
| 202 | + $query->joinfield = "$query->alias.smw_id"; |
| 203 | + $query->where = "$query->alias.smw_namespace=" . $this->m_dbs->addQuotes($description->getNamespace()); |
| 204 | + } elseif ( ($description instanceof SMWConjunction) || ($description instanceof SMWDisjunction) ) { |
| 205 | + $query->type = ($description instanceof SMWConjunction)?SMW_SQL2_CONJUNCTION:SMW_SQL2_DISJUNCTION; |
| 206 | + foreach ($description->getDescriptions() as $subdesc) { |
| 207 | + $sub = $this->compileQueries($subdesc); |
| 208 | + if ($sub >= 0) { |
| 209 | + $query->components[$sub] = true; |
| 210 | + } |
| 211 | + } |
| 212 | + } elseif ($description instanceof SMWClassDescription) { |
| 213 | + $cqid = SMWSQLStore2Query::$qnum; |
| 214 | + $cquery = new SMWSQLStore2Query(); |
| 215 | + $cquery->type = SMW_SQL2_CLASS_HIERARCHY; |
| 216 | + $cquery->joinfield = array(); |
| 217 | + foreach ($description->getCategories() as $cat) { |
| 218 | + $cid = $this->m_store->getSMWPageID($cat->getDBkey(), NS_CATEGORY, $cat->getInterwiki()); |
| 219 | + if ($cid != 0) { |
| 220 | + $cquery->joinfield[] = $cid; |
| 221 | + } |
| 222 | + } |
| 223 | + if (count($cquery->joinfield) == 0) { // empty result |
| 224 | + $query->type = SMW_SQL2_VALUE; |
| 225 | + $query->jointable = ''; |
| 226 | + $query->joinfield = ''; |
| 227 | + } else { // instance query with dicjunction of classes (categories) |
| 228 | + $query->jointable = 'smw_inst2'; |
| 229 | + $query->joinfield = "$query->alias.s_id"; |
| 230 | + $query->components[$cqid] = "$query->alias.o_id"; |
| 231 | + $this->m_queries[$cqid] = $cquery; |
| 232 | + } |
| 233 | + } elseif ($description instanceof SMWValueDescription) { // only type '_wpg' objects can appear on query level (essentially as nominal classes) |
| 234 | + if ($description->getDatavalue()->getTypeID() == '_wpg') { |
| 235 | + if ($description->getComparator() == SMW_CMP_EQ) { |
| 236 | + $query->type = SMW_SQL2_VALUE; |
| 237 | + $oid = $this->m_store->getSMWPageID($description->getDatavalue()->getDBkey(), $description->getDatavalue()->getNamespace(),$description->getDatavalue()->getInterwiki()); |
| 238 | + $query->joinfield = array($oid); |
| 239 | + } else { // join with smw_ids needed for other comparators (apply to title string) |
| 240 | + $query->jointable = 'smw_ids'; |
| 241 | + $query->joinfield = "$query->alias.smw_id"; |
| 242 | + $value = $description->getDatavalue()->getSortkey(); |
| 243 | + switch ($description->getComparator()) { |
| 244 | + case SMW_CMP_LEQ: $comp = '<='; break; |
| 245 | + case SMW_CMP_GEQ: $comp = '>='; break; |
| 246 | + case SMW_CMP_NEQ: $comp = '!='; break; |
| 247 | + case SMW_CMP_LIKE: |
| 248 | + $comp = ' LIKE '; |
| 249 | + $value = str_replace(array('%', '_', '*', '?'), array('\%', '\_', '%', '_'), $value); |
| 250 | + break; |
| 251 | + } |
| 252 | + $query->where = "$query->alias.smw_sortkey$comp" . $this->m_dbs->addQuotes($value); |
| 253 | + } |
| 254 | + } |
| 255 | + } elseif ($description instanceof SMWConceptDescription) { // fetch concept definition and insert it here |
| 256 | + $cid = $this->m_store->getSMWPageID($description->getConcept()->getDBKey(), SMW_NS_CONCEPT, ''); |
| 257 | + $row = $this->m_dbs->selectRow('smw_conc2', |
| 258 | + array('concept_txt','concept_features','concept_size','concept_depth','cache_date'), |
| 259 | + array('s_id'=>$cid), 'SMWSQLStore2Queries::compileQueries'); |
| 260 | + if ( $row === false ) { // no description found, concept does not exist |
| 261 | + // keep the above query object, it yields an empty result |
| 262 | + ///TODO: announce an error here? (maybe not, since the query processor can check for |
| 263 | + ///non-existing concept pages which is probably the main reason for finding nothing here |
| 264 | + } else { |
| 265 | + global $smwgQConceptCaching, $smwgQMaxSize, $smwgQMaxDepth, $smwgQFeatures, $smwgQConceptCacheLifetime; |
| 266 | + $may_be_computed = ($smwgQConceptCaching == CONCEPT_CACHE_NONE) || |
| 267 | + ( ($smwgQConceptCaching == CONCEPT_CACHE_HARD) && ( (~(~($row->concept_features+0) | $smwgQFeatures)) == 0) && |
| 268 | + ($smwgQMaxSize >= $row->concept_size) && ($smwgQMaxDepth >= $row->concept_depth)); |
| 269 | + if ($row->cache_date && |
| 270 | + ($row->cache_date > (strtotime("now") - $smwgQConceptCacheLifetime*60) || |
| 271 | + !$may_be_computed)) { // cached concept, use cache unless it is dead and can be revived |
| 272 | + $query->jointable = 'smw_conccache'; |
| 273 | + $query->joinfield = "$query->alias.s_id"; |
| 274 | + $query->where = "$query->alias.o_id=" . $this->m_dbs->addQuotes($cid); |
| 275 | + } elseif ($row->concept_txt) { // parse description and process it recursively |
| 276 | + if ($may_be_computed) { |
| 277 | + $qp = new SMWQueryParser(); |
| 278 | + // no defaultnamespaces here; if any, these are already in the concept |
| 279 | + $desc = $qp->getQueryDescription($row->concept_txt); |
| 280 | + $qid = $this->compileQueries($desc); |
| 281 | + $query = $this->m_queries[$qid]; |
| 282 | + } else { |
| 283 | + wfLoadExtensionMessages('SemanticMediaWiki'); |
| 284 | + $this->m_errors[] = wfMsg('smw_concept_cache_miss',$description->getConcept()->getText()); |
| 285 | + } |
| 286 | + } // else: no cache, no description (this may happen); treat like empty concept |
| 287 | + } |
| 288 | + } else { // (e.g. SMWThingDescription, SMWValueList is also treated elswhere) |
| 289 | + $qid = -1; // no condition |
| 290 | + } |
| 291 | + if ($qid >= 0) { |
| 292 | + $this->m_queries[$qid] = $query; |
| 293 | + } |
| 294 | + if ($query->type != SMW_SQL2_DISJUNCTION) { // sortkeys are killed by disjunctions (not all parts may have them), preprocessing might try to push disjunctions downwards to safe sortkey |
| 295 | + foreach ($query->components as $cid => $field) { |
| 296 | + $query->sortfields = array_merge($this->m_queries[$cid]->sortfields,$query->sortfields); |
| 297 | + } |
| 298 | + } |
| 299 | + return $qid; |
| 300 | + } |
| 301 | + |
| 302 | + /** |
| 303 | + * Modify the given query object to account for some property condition for the given property. |
| 304 | + * The parameter $property may be a property object or an internal storage id. This is what makes |
| 305 | + * this method useful: it can be used even with internal properties that have no MediaWiki Title. |
| 306 | + * $typeid is set if property ids are used, since internal properties may not have a defined type. |
| 307 | + * Some properties cannot be queried for; in this case, the query type is changed to SMW_SQL2_NOQUERY. |
| 308 | + * Callers need to check for this. |
| 309 | + */ |
| 310 | + protected function compilePropertyCondition(&$query, $property, SMWDescription $valuedesc, $typeid=false) { |
| 311 | + $query->joinfield = "$query->alias.s_id"; |
| 312 | + if ($property instanceof SMWPropertyValue) { |
| 313 | + $typeid = $property->getTypeID(); |
| 314 | + $mode = SMWSQLStore2::getStorageMode($typeid); |
| 315 | + $pid = $this->m_store->getSMWPropertyID($property); |
| 316 | + $sortkey = $property->getXSDValue(); |
| 317 | + if ($mode != SMW_SQL2_SUBS2) { // also make property hierarchy (though not for all properties) |
| 318 | + $pqid = SMWSQLStore2Query::$qnum; |
| 319 | + $pquery = new SMWSQLStore2Query(); |
| 320 | + $pquery->type = SMW_SQL2_PROP_HIERARCHY; |
| 321 | + $pquery->joinfield = array($pid); |
| 322 | + $query->components[$pqid] = "$query->alias.p_id"; |
| 323 | + $this->m_queries[$pqid] = $pquery; |
| 324 | + } |
| 325 | + } else { |
| 326 | + $pid = $property; |
| 327 | + $sortkey = false; |
| 328 | + $mode = SMWSQLStore2::getStorageMode($typeid); |
| 329 | + if ($mode != SMW_SQL2_SUBS2) { // no property hierarchy, but normal query (not for all properties) |
| 330 | + $query->where = "$query->alias.p_id=" . $this->m_dbs->addQuotes($pid); |
| 331 | + } |
| 332 | + } |
| 333 | + $mode = SMWSQLStore2::getStorageMode($typeid); |
| 334 | + $sortfield = ''; // used if we should sort by this property |
| 335 | + switch ($mode) { |
| 336 | + case SMW_SQL2_RELS2: case SMW_SQL2_SUBS2: // subconditions as subqueries (compiled) |
| 337 | + $query->jointable = ($mode==SMW_SQL2_RELS2)?'smw_rels2':'smw_subs2'; |
| 338 | + $sub = $this->compileQueries($valuedesc); |
| 339 | + if ($sub >= 0) { |
| 340 | + $query->components[$sub] = "$query->alias.o_id"; |
| 341 | + } |
| 342 | + if ( $sortkey && array_key_exists($sortkey, $this->m_sortkeys) ) { |
| 343 | + $query->from = ' INNER JOIN ' . $this->m_dbs->tableName('smw_ids') . " AS ids$query->alias ON ids$query->alias.smw_id=$query->alias.o_id"; |
| 344 | + $sortfield = "ids$query->alias.smw_title"; /// TODO: as below, smw_ids here is possibly duplicated! Can we prevent that? (PERFORMANCE) |
| 345 | + } |
| 346 | + break; |
| 347 | + case SMW_SQL2_NARY2: |
| 348 | + $query->jointable = 'smw_rels2'; |
| 349 | + if ($valuedesc instanceof SMWValueList) { // anything else is ignored! |
| 350 | + $typevalue = $property->getTypesValue(); |
| 351 | + $typelabels = $typevalue->getTypeLabels(); |
| 352 | + reset($typelabels); |
| 353 | + $subqid = SMWSQLStore2Query::$qnum; |
| 354 | + $subquery = new SMWSQLStore2Query(); |
| 355 | + $subquery->type = SMW_SQL2_CONJUNCTION; |
| 356 | + $query->components[$subqid] = "$query->alias.o_id"; |
| 357 | + $this->m_queries[$subqid] = $subquery; |
| 358 | + for ($i=0; $i<$valuedesc->getCount(); $i++) { |
| 359 | + $desc = $valuedesc->getDescription($i); |
| 360 | + if ($desc !== NULL) { |
| 361 | + $stypeid = SMWDataValueFactory::findTypeID(current($typelabels)); |
| 362 | + $valpid = $this->m_store->getSMWPageID(strval($i),SMW_NS_PROPERTY,SMW_SQL2_SMWIW); |
| 363 | + $valqid = SMWSQLStore2Query::$qnum; |
| 364 | + $valquery = new SMWSQLStore2Query(); |
| 365 | + $this->compilePropertyCondition($valquery, $valpid, $desc, $stypeid); |
| 366 | + if ($valquery->type != SMW_SQL2_NOQUERY) { |
| 367 | + $subquery->components[$valqid] = true; |
| 368 | + $this->m_queries[$valqid] = $valquery; |
| 369 | + } |
| 370 | + } |
| 371 | + next($typelabels); |
| 372 | + } |
| 373 | + } |
| 374 | + break; |
| 375 | + case SMW_SQL2_TEXT2: // no subconditions |
| 376 | + $query->jointable = 'smw_text2'; |
| 377 | + break; |
| 378 | + case SMW_SQL2_ATTS2: case SMW_SQL2_SPEC2: // subquery only conj/disj of values, compile to single "where" |
| 379 | + $query->jointable = ($mode==SMW_SQL2_ATTS2)?'smw_atts2':'smw_spec2'; |
| 380 | + $aw = $this->compileAttributeWhere($valuedesc,"$query->alias"); |
| 381 | + if ($aw != '') { |
| 382 | + $query->where .= ($query->where?' AND ':'') . $aw; |
| 383 | + } |
| 384 | + if ( $sortkey && array_key_exists($sortkey, $this->m_sortkeys) ) { |
| 385 | + if ($mode==SMW_SQL2_ATTS2) { |
| 386 | + $sortfield = "$query->alias." . (SMWDataValueFactory::newTypeIDValue($typeid)->isNumeric()?'value_num':'value_xsd'); |
| 387 | + } else { |
| 388 | + $sortfield = "$query->alias.value_string"; |
| 389 | + } |
| 390 | + } |
| 391 | + break; |
| 392 | + default: // drop this query |
| 393 | + $query->type = SMW_SQL2_NOQUERY; |
| 394 | + $sortfield = false; |
| 395 | + } |
| 396 | + if ($sortfield) { |
| 397 | + $query->sortfields[$sortkey] = $sortfield; |
| 398 | + } |
| 399 | + } |
| 400 | + |
| 401 | + /** |
| 402 | + * Given an SMWDescription that is just a conjunction or disjunction of |
| 403 | + * SMWValueDescription objects, create a plain WHERE condition string for it. |
| 404 | + */ |
| 405 | + protected function compileAttributeWhere(SMWDescription $description, $jointable) { |
| 406 | + if ($description instanceof SMWValueDescription) { |
| 407 | + $dv = $description->getDatavalue(); |
| 408 | + if (SMWSQLStore2::getStorageMode($dv->getTypeID()) == SMW_SQL2_SPEC2) { |
| 409 | + $value = $dv->getXSDValue(); |
| 410 | + $field = "$jointable.value_string"; |
| 411 | + } else { //should be SMW_SQL2_ATTS2 |
| 412 | + $value = $dv->isNumeric() ? $dv->getNumericValue() : $dv->getXSDValue(); |
| 413 | + $field = $dv->isNumeric() ? "$jointable.value_num" : "$jointable.value_xsd"; |
| 414 | + } |
| 415 | + switch ($description->getComparator()) { |
| 416 | + case SMW_CMP_LEQ: $comp = '<='; break; |
| 417 | + case SMW_CMP_GEQ: $comp = '>='; break; |
| 418 | + case SMW_CMP_NEQ: $comp = '!='; break; |
| 419 | + case SMW_CMP_LIKE: |
| 420 | + if ($dv->getTypeID() == '_str') { |
| 421 | + $comp = ' LIKE '; |
| 422 | + $value = str_replace(array('%', '_', '*', '?'), array('\%', '\_', '%', '_'), $value); |
| 423 | + } else { // LIKE only supported for strings |
| 424 | + $comp = '='; |
| 425 | + } |
| 426 | + break; |
| 427 | + case SMW_CMP_EQ: default: $comp = '='; break; |
| 428 | + } |
| 429 | + $result = "$field$comp" . $this->m_dbs->addQuotes($value); |
| 430 | + } elseif ( ($description instanceof SMWConjunction) || ($description instanceof SMWDisjunction) ) { |
| 431 | + $op = ($description instanceof SMWConjunction) ? ' AND ' : ' OR '; |
| 432 | + $result = ''; |
| 433 | + foreach ($description->getDescriptions() as $subdesc) { |
| 434 | + $result= $result . ( $result!=''?$op:'' ) . $this->compileAttributeWhere($subdesc,$jointable); |
| 435 | + } |
| 436 | + $result = "($result)"; |
| 437 | + } else { |
| 438 | + $result = ''; |
| 439 | + } |
| 440 | + return $result; |
| 441 | + } |
| 442 | + |
| 443 | + /** |
| 444 | + * Process stored queries and change store accordingly. The query obj is modified |
| 445 | + * so that it contains non-recursive description of a select to execute for getting |
| 446 | + * the actual result. |
| 447 | + */ |
| 448 | + protected function executeQueries(SMWSQLStore2Query &$query) { |
| 449 | + switch ($query->type) { |
| 450 | + case SMW_SQL2_TABLE: // normal query with conjunctive subcondition |
| 451 | + foreach ($query->components as $qid => $joinfield) { |
| 452 | + $subquery = $this->m_queries[$qid]; |
| 453 | + $this->executeQueries($subquery); |
| 454 | + if ($subquery->jointable != '') { // join with jointable.joinfield |
| 455 | + $query->from .= ' INNER JOIN ' . $this->m_dbs->tableName($subquery->jointable) . " AS $subquery->alias ON $joinfield=" . $subquery->joinfield; |
| 456 | + } elseif ($subquery->joinfield !== '') { // require joinfield as "value" via WHERE |
| 457 | + $condition = ''; |
| 458 | + foreach ($subquery->joinfield as $value) { |
| 459 | + $condition .= ($condition?' OR ':'') . "$joinfield=" . $this->m_dbs->addQuotes($value); |
| 460 | + } |
| 461 | + if (count($subquery->joinfield) > 1) { |
| 462 | + $condition = "($condition)"; |
| 463 | + } |
| 464 | + $query->where .= (($query->where == '')?'':' AND ') . $condition; |
| 465 | + } else { // interpret empty joinfields as impossible condition (empty result) |
| 466 | + $query->joinfield = ''; // make whole query false |
| 467 | + $query->jointable = ''; |
| 468 | + $query->where = ''; |
| 469 | + $query->from = ''; |
| 470 | + break; |
| 471 | + } |
| 472 | + if ($subquery->where != '') { |
| 473 | + $query->where .= (($query->where == '')?'':' AND ') . $subquery->where; |
| 474 | + } |
| 475 | + $query->from .= $subquery->from; |
| 476 | + } |
| 477 | + $query->components = array(); |
| 478 | + break; |
| 479 | + case SMW_SQL2_CONJUNCTION: |
| 480 | + // pick one subquery with jointable as anchor point ... |
| 481 | + reset($query->components); |
| 482 | + $key = false; |
| 483 | + foreach ($query->components as $qkey => $qid) { |
| 484 | + if ($this->m_queries[$qkey]->jointable != '') { |
| 485 | + $key = $qkey; |
| 486 | + break; |
| 487 | + } |
| 488 | + } |
| 489 | + if ($key !== false) { |
| 490 | + $result = $this->m_queries[$key]; |
| 491 | + unset($query->components[$key]); |
| 492 | + $this->executeQueries($result); // execute it first (may change jointable and joinfield, e.g. when making temporary tables) |
| 493 | + // ... and append to this query the remaining queries |
| 494 | + foreach ($query->components as $qid => $joinfield) { |
| 495 | + $result->components[$qid] = $result->joinfield; |
| 496 | + } |
| 497 | + $this->executeQueries($result); // second execute, now incorporating remaining conditions |
| 498 | + } else { // only fixed values in conjunction, make a new value without joining |
| 499 | + $key = $qkey; |
| 500 | + $result = $this->m_queries[$key]; |
| 501 | + unset($query->components[$key]); |
| 502 | + foreach ($query->components as $qid => $joinfield) { |
| 503 | + if ($result->joinfield != $this->m_queries[$qid]->joinfield) { |
| 504 | + $result->joinfield = ''; // all other values should already be '' |
| 505 | + break; |
| 506 | + } |
| 507 | + } |
| 508 | + } |
| 509 | + $query = $result; |
| 510 | + break; |
| 511 | + case SMW_SQL2_DISJUNCTION:
|
| 512 | + // modified by dch, disable TEMPORARY tables
|
| 513 | +// if ($this->m_qmode !== SMWQuery::MODE_DEBUG) { |
| 514 | +// $this->m_dbs->query( "CREATE TEMPORARY TABLE " . $this->m_dbs->tableName($query->alias) . |
| 515 | +// ' ( id INT UNSIGNED KEY ) TYPE=MEMORY', 'SMW::executeQueries' ); |
| 516 | +// } |
| 517 | +// $this->m_querylog[$query->alias] = array(); |
| 518 | + foreach ($query->components as $qid => $joinfield) { |
| 519 | + $subquery = $this->m_queries[$qid]; |
| 520 | + $this->executeQueries($subquery); |
| 521 | + $sql = ''; |
| 522 | + if ($subquery->jointable != '') { |
| 523 | +// $sql = "INSERT IGNORE INTO " . $this->m_dbs->tableName($query->alias) . " SELECT $subquery->joinfield FROM " . |
| 524 | +// $this->m_dbs->tableName($subquery->jointable) . " AS $subquery->alias $subquery->from" . ($subquery->where?" WHERE $subquery->where":''); |
| 525 | + $sql = ($sql != ''?'UNION':''). "SELECT $subquery->joinfield AS id FROM " .
|
| 526 | + $this->m_dbs->tableName($subquery->jointable) . " AS $subquery->alias $subquery->from" . ($subquery->where?" WHERE $subquery->where":'');
|
| 527 | + } elseif ($subquery->joinfield !== '') { |
| 528 | + /// NOTE: this works only for single "unconditional" values without further |
| 529 | + /// WHERE or FROM. The execution must take care of not creating any others. |
| 530 | + $values = ''; |
| 531 | + foreach ($subquery->joinfield as $value) { |
| 532 | + $values .= ($values?',':'') . '(' . $this->m_dbs->addQuotes($value) . ')'; |
| 533 | + } |
| 534 | +// $sql = "INSERT IGNORE INTO " . $this->m_dbs->tableName($query->alias) . " (id) VALUES $values"; |
| 535 | + $sql = ($sql != ''?'UNION':''). "SELECT smw_id AS id FROM ".$this->m_dbs->tableName('smw_ids')." WHERE smw_id IN ($values)";
|
| 536 | + } // else: // interpret empty joinfields as impossible condition (empty result), ignore |
| 537 | +// if ($sql) { |
| 538 | +// $this->m_querylog[$query->alias][] = $sql; |
| 539 | +// if ($this->m_qmode !== SMWQuery::MODE_DEBUG) { |
| 540 | +// $this->m_dbs->query($sql , 'SMW::executeQueries'); |
| 541 | +// } |
| 542 | +// } |
| 543 | + } |
| 544 | +// $query->jointable = $query->alias; |
| 545 | + $query->jointable = "($sql)";
|
| 546 | + $query->joinfield = "$query->alias.id"; |
| 547 | + $query->sortfields = array(); // make sure we got no sortfields |
| 548 | + /// TODO: currently this eliminates sortkeys, possibly keep them (needs different temp table format though, maybe not such a good thing to do) |
| 549 | + break; |
| 550 | + case SMW_SQL2_PROP_HIERARCHY: case SMW_SQL2_CLASS_HIERARCHY: // make a saturated hierarchy |
| 551 | + $this->executeHierarchyQuery($query); |
| 552 | + break; |
| 553 | + case SMW_SQL2_VALUE: break; // nothing to do |
| 554 | + } |
| 555 | + } |
| 556 | + |
| 557 | + /** |
| 558 | + * Find subproperties or subcategories. This may require iterative computation, |
| 559 | + * and temporary tables are used in many cases. |
| 560 | + */ |
| 561 | + protected function executeHierarchyQuery(&$query) { |
| 562 | + $fname = "SMWSQLStore2Queries::executeQueries-hierarchy-$query->type (SMW)"; |
| 563 | + wfProfileIn($fname); |
| 564 | + global $smwgQSubpropertyDepth, $smwgQSubcategoryDepth; |
| 565 | + $depth = ($query->type == SMW_SQL2_PROP_HIERARCHY)?$smwgQSubpropertyDepth:$smwgQSubcategoryDepth; |
| 566 | + if ($depth <= 0) { // treat as value, no recursion |
| 567 | + $query->type = SMW_SQL2_VALUE; |
| 568 | + wfProfileOut($fname); |
| 569 | + return; |
| 570 | + } |
| 571 | + $values = ''; |
| 572 | + $valuecond = ''; |
| 573 | + foreach ($query->joinfield as $value) { |
| 574 | + $values .= ($values?',':'') . '(' . $this->m_dbs->addQuotes($value) . ')'; |
| 575 | + $valuecond .= ($valuecond?' OR ':'') . 'o_id=' . $this->m_dbs->addQuotes($value); |
| 576 | + } |
| 577 | + $smw_subs2 = $this->m_dbs->tableName('smw_subs2'); |
| 578 | + // try to safe time (SELECT is cheaper than creating/dropping 3 temp tables): |
| 579 | + $res = $this->m_dbs->select($smw_subs2,'s_id',$valuecond, array('LIMIT'=>1)); |
| 580 | + if (!$this->m_dbs->fetchObject($res)) { // no subobjects, we are done! |
| 581 | + $this->m_dbs->freeResult($res); |
| 582 | + $query->type = SMW_SQL2_VALUE; |
| 583 | + wfProfileOut($fname); |
| 584 | + return; |
| 585 | + } |
| 586 | + $this->m_dbs->freeResult($res); |
| 587 | + $tablename = $this->m_dbs->tableName($query->alias);
|
| 588 | + // modified by dch
|
| 589 | +// $this->m_querylog[$query->alias] = array("Recursively computed hierarchy for element(s) $values.");
|
| 590 | + $this->m_querylog[$query->alias]["$depth:$values"] = "Recursively computed hierarchy for element(s) $values.";
|
| 591 | +
|
| 592 | + $query->jointable = $query->alias;
|
| 593 | + $query->joinfield = "$query->alias.id"; |
| 594 | + if ($this->m_qmode == SMWQuery::MODE_DEBUG) { |
| 595 | + wfProfileOut($fname); |
| 596 | + return; // no real queries in debug mode |
| 597 | + }
|
| 598 | + |
| 599 | + $this->m_dbs->query( "CREATE TEMPORARY TABLE $tablename " . |
| 600 | + '( id INT UNSIGNED NOT NULL KEY) TYPE=MEMORY', 'SMW::executeQueries' ); |
| 601 | + if (array_key_exists($values, $this->m_hierarchies)) { // just copy known result
|
| 602 | + $this->m_dbs->query("INSERT INTO $tablename (id) SELECT id" .
|
| 603 | + ' FROM ' . $this->m_hierarchies[$values],
|
| 604 | + 'SMW::executeQueries');
|
| 605 | + wfProfileOut($fname); |
| 606 | + return; |
| 607 | + } |
| 608 | + |
| 609 | + /// NOTE: we use two helper tables. One holds the results of each new iteration, one holds the |
| 610 | + /// results of the previous iteration. One could of course do with only the above result table, |
| 611 | + /// but then every iteration would use all elements of this table, while only the new ones |
| 612 | + /// obtained in the previous step are relevant. So this is a performance measure. |
| 613 | + $tmpnew = 'smw_new'; |
| 614 | + $tmpres = 'smw_res'; |
| 615 | + $this->m_dbs->query( "CREATE TEMPORARY TABLE $tmpnew " . |
| 616 | + '( id INT UNSIGNED ) TYPE=MEMORY', 'SMW::executeQueries' ); |
| 617 | + $this->m_dbs->query( "CREATE TEMPORARY TABLE $tmpres " . |
| 618 | + '( id INT UNSIGNED ) TYPE=MEMORY', 'SMW::executeQueries' ); |
| 619 | + |
| 620 | + $this->m_dbs->query("INSERT IGNORE INTO $tablename (id) VALUES $values", 'SMW::executeQueries'); |
| 621 | + $this->m_dbs->query("INSERT IGNORE INTO $tmpnew (id) VALUES $values", 'SMW::executeQueries'); |
| 622 | + |
| 623 | + for ($i=0; $i<$depth; $i++) { |
| 624 | + $this->m_dbs->query("INSERT INTO $tmpres (id) SELECT s_id FROM $smw_subs2, $tmpnew WHERE o_id=id", |
| 625 | + 'SMW::executeQueries'); |
| 626 | + if ($this->m_dbs->affectedRows() == 0) { // no change, exit loop |
| 627 | + break; |
| 628 | + } |
| 629 | + $this->m_dbs->query("INSERT IGNORE INTO $tablename (id) SELECT $tmpres.id FROM $tmpres", |
| 630 | + 'SMW::executeQueries'); |
| 631 | + if ($this->m_dbs->affectedRows() == 0) { // no change, exit loop |
| 632 | + break; |
| 633 | + }
|
| 634 | +
|
| 635 | + $this->m_dbs->query('TRUNCATE TABLE ' . $tmpnew, 'SMW::executeQueries'); // empty "new" table |
| 636 | + $tmpname = $tmpnew; |
| 637 | + $tmpnew = $tmpres; |
| 638 | + $tmpres = $tmpname; |
| 639 | + }
|
| 640 | + |
| 641 | + $this->m_hierarchies[$values] = $tablename; |
| 642 | + $this->m_dbs->query('DROP TEMPORARY TABLE smw_new', 'SMW::executeQueries'); |
| 643 | + $this->m_dbs->query('DROP TEMPORARY TABLE smw_res', 'SMW::executeQueries'); |
| 644 | + wfProfileOut($fname); |
| 645 | + } |
| 646 | + |
| 647 | + /** |
| 648 | + * This function modifies the given query object at $qid to account for all ordering conditions |
| 649 | + * in the SMWQuery $query. It is always required that $qid is the id of a query that joins with |
| 650 | + * smw_ids so that the field alias.smw_title is $available for default sorting. |
| 651 | + */ |
| 652 | + protected function applyOrderConditions($query, $qid) { |
| 653 | + global $smwgQSortingSupport; |
| 654 | + if ( !$smwgQSortingSupport ) { |
| 655 | + return; |
| 656 | + } |
| 657 | + $qobj = $this->m_queries[$qid]; |
| 658 | + // (1) collect required extra property descriptions: |
| 659 | + $extraproperties = array(); |
| 660 | + foreach ($this->m_sortkeys as $propkey => $order) { |
| 661 | + if (!array_key_exists($propkey,$qobj->sortfields)) { // find missing property to sort by |
| 662 | + if ($propkey == '') { // sort by first result column (page titles) |
| 663 | + $qobj->sortfields[$propkey] = "$qobj->alias.smw_sortkey"; |
| 664 | + } else { // try to extend query |
| 665 | + $extrawhere = ''; |
| 666 | + $sortprop = SMWPropertyValue::makeUserProperty($propkey); |
| 667 | + if ($sortprop->isValid()) { |
| 668 | + $extraproperties[] = new SMWSomeProperty($sortprop, new SMWThingDescription()); |
| 669 | + } |
| 670 | + } |
| 671 | + } |
| 672 | + } |
| 673 | + // (2) compile according conditions and hack them into $qobj: |
| 674 | + if (count($extraproperties) > 0) { |
| 675 | + $desc = new SMWConjunction($extraproperties); |
| 676 | + $newqid = $this->compileQueries($desc); |
| 677 | + $newqobj = $this->m_queries[$newqid]; // this is always an SMW_SQL2_CONJUNCTION ... |
| 678 | + foreach ($newqobj->components as $cid => $field) { // ... so just re-wire its dependencies |
| 679 | + $qobj->components[$cid] = $qobj->joinfield; |
| 680 | + $qobj->sortfields = array_merge($qobj->sortfields, $this->m_queries[$cid]->sortfields); |
| 681 | + } |
| 682 | + $this->m_queries[$qid] = $qobj; |
| 683 | + } |
| 684 | + } |
| 685 | + |
| 686 | + /** |
| 687 | + * Get a SQL option array for the given query and preprocessed query object at given id. |
| 688 | + */ |
| 689 | + protected function getSQLOptions($query,$rootid) { |
| 690 | + global $smwgQSortingSupport, $smwgQRandSortingSupport; |
| 691 | + $result = array( 'LIMIT' => $query->getLimit() + 1, 'OFFSET' => $query->getOffset() ); |
| 692 | + // build ORDER BY options using discovered sorting fields: |
| 693 | + if ($smwgQSortingSupport) { |
| 694 | + $qobj = $this->m_queries[$rootid]; |
| 695 | + foreach ($this->m_sortkeys as $propkey => $order) { |
| 696 | + if (array_key_exists($propkey,$qobj->sortfields)) { // field successfully added |
| 697 | + if (!array_key_exists('ORDER BY', $result)) { |
| 698 | + $result['ORDER BY'] = ''; |
| 699 | + } else { |
| 700 | + $result['ORDER BY'] .= ', '; |
| 701 | + } |
| 702 | + if ('RAND()' == $order){ |
| 703 | + $result['ORDER BY'] .= " $order "; |
| 704 | + } else { |
| 705 | + $result['ORDER BY'] .= $qobj->sortfields[$propkey] . " $order "; |
| 706 | + } |
| 707 | + |
| 708 | + } |
| 709 | + } |
| 710 | + } |
| 711 | + return $result; |
| 712 | + } |
| 713 | + |
| 714 | + /** |
| 715 | + * After querying, make sure no temporary database tables are left. |
| 716 | + */ |
| 717 | + protected function cleanUp() {
|
| 718 | + if ($this->m_qmode !== SMWQuery::MODE_DEBUG) { |
| 719 | + foreach ($this->m_querylog as $table => $log) { |
| 720 | + $this->m_dbs->query("DROP TEMPORARY TABLE " . $this->m_dbs->tableName($table), 'SMW::getQueryResult'); |
| 721 | + } |
| 722 | + } |
| 723 | + } |
| 724 | + |
| 725 | +} |
Index: trunk/extensions/SemanticNotifyMe/includes/storage/SMW_NMStorageSQL.php |
— | — | @@ -0,0 +1,700 @@ |
| 2 | +<?php
|
| 3 | +/**
|
| 4 | + * This file provides the access to the MediaWiki SQL database tables that are
|
| 5 | + * used by the NotifyMe extension.
|
| 6 | + *
|
| 7 | + * @author dch
|
| 8 | + *
|
| 9 | + */
|
| 10 | +if ( !defined( 'MEDIAWIKI' ) ) die;
|
| 11 | +global $smwgNMIP;
|
| 12 | +require_once $smwgNMIP . '/includes/SMW_NMDBHelper.php';
|
| 13 | +
|
| 14 | +/**
|
| 15 | + * This class encapsulates all methods that care about the database tables of
|
| 16 | + * the NotifyMe extension.
|
| 17 | + *
|
| 18 | + */
|
| 19 | +class NMStorageSQL {
|
| 20 | +
|
| 21 | + public function setup($verbose) {
|
| 22 | +
|
| 23 | + $db =& wfGetDB( DB_MASTER );
|
| 24 | +
|
| 25 | + SNMDBHelper::reportProgress("Setting up NotifyMe database ...\n",$verbose);
|
| 26 | +
|
| 27 | + extract( $db->tableNames('smw_nm_monitor', 'smw_nm_query', 'smw_nm_relations', 'smw_nm_rss') );
|
| 28 | +
|
| 29 | + // page_id, monitored page id
|
| 30 | + SNMDBHelper::setupTable($smw_nm_monitor,
|
| 31 | + array('notify_id' => 'INT(8) UNSIGNED NOT NULL',
|
| 32 | + 'page_id' => 'INT(8) UNSIGNED NOT NULL'), $db, $verbose);
|
| 33 | + SNMDBHelper::setupIndex($smw_nm_monitor, array('page_id'), $db);
|
| 34 | + SNMDBHelper::setupTable($smw_nm_query,
|
| 35 | + array('notify_id' => 'INT(8) UNSIGNED NOT NULL KEY AUTO_INCREMENT',
|
| 36 | + 'user_id' => 'INT(8) UNSIGNED NOT NULL',
|
| 37 | + 'delegate' => 'BLOB',
|
| 38 | + 'name' => 'VARCHAR(255) binary NOT NULL',
|
| 39 | + 'rep_all' => 'TINYINT(1) NOT NULL default \'1\'',
|
| 40 | + 'show_all' => 'TINYINT(1) NOT NULL default \'0\'',
|
| 41 | + 'query' => 'BLOB NOT NULL',
|
| 42 | + 'nm_sql' => 'BLOB',
|
| 43 | + 'nm_hierarchy' => 'BLOB',
|
| 44 | + 'enable' => 'TINYINT(1) NOT NULL default \'0\''), $db, $verbose);
|
| 45 | + SNMDBHelper::setupIndex($smw_nm_query, array('user_id'), $db);
|
| 46 | + // page_id, related page / property id in notify query
|
| 47 | + SNMDBHelper::setupTable($smw_nm_relations,
|
| 48 | + array('notify_id' => 'INT(8) UNSIGNED NOT NULL',
|
| 49 | + 'smw_id' => 'INT(8) UNSIGNED NOT NULL',
|
| 50 | + // 0 category, 1 instance, 2 property
|
| 51 | + 'type' => 'INT(8) UNSIGNED NOT NULL',
|
| 52 | + 'subquery' => 'INT(8) UNSIGNED NOT NULL default \'0\''), $db, $verbose);
|
| 53 | + SNMDBHelper::setupIndex($smw_nm_relations, array('smw_id', 'notify_id'), $db);
|
| 54 | + SNMDBHelper::setupTable($smw_nm_rss,
|
| 55 | + array('msg_id' => 'INT(8) UNSIGNED NOT NULL KEY AUTO_INCREMENT',
|
| 56 | + 'mailed' => 'TINYINT(1) NOT NULL default \'0\'',
|
| 57 | + 'user_id' => 'INT(8) UNSIGNED',
|
| 58 | + 'notify_id' => 'INT(8) UNSIGNED',
|
| 59 | + 'title' => 'VARCHAR(255) binary NOT NULL',
|
| 60 | + 'link' => 'BLOB',
|
| 61 | + 'notify' => 'BLOB NOT NULL',
|
| 62 | + 'timestamp' => 'VARCHAR(14) binary NOT NULL'), $db, $verbose);
|
| 63 | + SNMDBHelper::setupIndex($smw_nm_rss, array('user_id'), $db);
|
| 64 | +
|
| 65 | + SNMDBHelper::reportProgress("... done!\n",$verbose);
|
| 66 | +
|
| 67 | + }
|
| 68 | +
|
| 69 | + public function addNotifyQuery($user_id, $querystring, $name, $rep_all, $show_all, $delegate) {
|
| 70 | + $fname = 'NotifyMe::addNotifyQuery';
|
| 71 | + wfProfileIn( $fname );
|
| 72 | +
|
| 73 | + $dbw =& wfGetDB( DB_MASTER );
|
| 74 | + $notify_id = $dbw->nextSequenceValue('nmquery_notify_id_seq');
|
| 75 | + if($dbw->insert( 'smw_nm_query', array(
|
| 76 | + 'notify_id' => $notify_id,
|
| 77 | + 'user_id' => $user_id,
|
| 78 | + 'query' => $querystring,
|
| 79 | + 'name' => $name,
|
| 80 | + 'rep_all' => $rep_all,
|
| 81 | + 'show_all' => $show_all,
|
| 82 | + 'delegate' => $delegate), $fname))
|
| 83 | + {
|
| 84 | + $notify_id = $dbw->insertId();
|
| 85 | + } else {
|
| 86 | + $notify_id = 0;
|
| 87 | + }
|
| 88 | + wfProfileOut( $fname );
|
| 89 | + return $notify_id;
|
| 90 | + }
|
| 91 | + public function removeNotifyQuery($notify_ids){
|
| 92 | + $fname = 'NotifyMe::delNotify';
|
| 93 | + wfProfileIn( $fname );
|
| 94 | + $db =& wfGetDB( DB_MASTER );
|
| 95 | + $db->delete( $db->tableName('smw_nm_monitor'), array('notify_id'=>$notify_ids), $fname);
|
| 96 | + $db->delete( $db->tableName('smw_nm_relations'), array('notify_id'=>$notify_ids), $fname);
|
| 97 | + $db->delete( $db->tableName('smw_nm_query'), array('notify_id'=>$notify_ids), $fname);
|
| 98 | + wfProfileOut( $fname );
|
| 99 | + return true;
|
| 100 | + }
|
| 101 | + public function addNotifyMonitor($add_monitor){
|
| 102 | + $fname = 'NotifyMe::addNotifyMonitor';
|
| 103 | + wfProfileIn( $fname );
|
| 104 | +
|
| 105 | + $db =& wfGetDB( DB_MASTER );
|
| 106 | + $res = $db->insert( 'smw_nm_monitor', $add_monitor, $fname);
|
| 107 | + wfProfileOut( $fname );
|
| 108 | + return $res;
|
| 109 | + }
|
| 110 | + public function removeNotifyMonitor($remove_monitored){
|
| 111 | + $fname = 'NotifyMe::addNotifyMonitor';
|
| 112 | + wfProfileIn( $fname );
|
| 113 | +
|
| 114 | + $db =& wfGetDB( DB_MASTER );
|
| 115 | + foreach($remove_monitored as $monitor) {
|
| 116 | + $db->delete( $db->tableName('smw_nm_monitor'), array('notify_id'=>$monitor['notify_id'], 'page_id'=>$monitor['page_id']), $fname);
|
| 117 | + }
|
| 118 | + wfProfileOut( $fname );
|
| 119 | + return $res;
|
| 120 | + }
|
| 121 | + public function addNotifyRelations($notify_id, $rels, $subquery=0){
|
| 122 | + $fname = 'NotifyMe::addNotifyRelations';
|
| 123 | + wfProfileIn( $fname );
|
| 124 | +
|
| 125 | + foreach($rels as $i=>$vi) {
|
| 126 | + foreach($rels as $j=>$vj) {
|
| 127 | + if($i != $j && $vi['namespace']==$vj['namespace'] && $vi['title']==$vj['title']) {
|
| 128 | + unset($rels[$i]);
|
| 129 | + break;
|
| 130 | + }
|
| 131 | + }
|
| 132 | + }
|
| 133 | +
|
| 134 | + $dbw =& wfGetDB( DB_MASTER );
|
| 135 | + $relations = array();
|
| 136 | + $new_rel = false;
|
| 137 | + foreach($rels as $smw){
|
| 138 | + $res = $dbw->select( $dbw->tableName('smw_ids'),
|
| 139 | + 'smw_id',
|
| 140 | + // array( 'smw_namespace' => $smw['namespace'], 'smw_title' => $smw['title']),
|
| 141 | + 'smw_namespace='.$smw['namespace'].' AND smw_title='.$dbw->addQuotes($smw['title']),
|
| 142 | + 'NotifyMe::getRelatedSmwId');
|
| 143 | + $rel_smw_id = 0;
|
| 144 | + if($dbw->numRows( $res ) > 0) {
|
| 145 | + $rel_smw_id = $dbw->fetchObject($res)->smw_id;
|
| 146 | + $dbw->freeResult($res);
|
| 147 | + } else {
|
| 148 | + $dbw->freeResult($res);
|
| 149 | + return false;
|
| 150 | + }
|
| 151 | +
|
| 152 | + if($rel_smw_id > 0) {
|
| 153 | + $new_rel = true;
|
| 154 | + $relations[] = array(
|
| 155 | + 'notify_id' => $notify_id,
|
| 156 | + 'smw_id' => $rel_smw_id,
|
| 157 | + // $type: 0 category, 1 instance, 2 property
|
| 158 | + 'type' => (($smw['namespace']==NS_CATEGORY)?0:(($smw['namespace']==SMW_NS_PROPERTY)?2:1)),
|
| 159 | + 'subquery' => $subquery);
|
| 160 | + }
|
| 161 | + }
|
| 162 | + if($new_rel) {
|
| 163 | + $dbw->insert( 'smw_nm_relations', $relations, $fname);
|
| 164 | + }
|
| 165 | + wfProfileOut( $fname );
|
| 166 | + return true;
|
| 167 | + }
|
| 168 | + public function getNotifications($user_id){
|
| 169 | + $fname = 'NotifyMe::getNotifications';
|
| 170 | + wfProfileIn( $fname );
|
| 171 | +
|
| 172 | + $result = array();
|
| 173 | +
|
| 174 | + $db = wfGetDB( DB_SLAVE );
|
| 175 | + $res = $db->select( $db->tableName('smw_nm_query'),
|
| 176 | + array('notify_id', 'delegate', 'name', 'query', 'enable', 'rep_all', 'show_all'),
|
| 177 | + array('user_id'=>$user_id), $fname, array('ORDER BY' => 'notify_id'));
|
| 178 | + if($db->numRows( $res ) > 0) {
|
| 179 | + while($row = $db->fetchObject($res)) {
|
| 180 | + $result[] = array('notify_id'=>$row->notify_id,
|
| 181 | + 'delegate'=>$row->delegate,
|
| 182 | + 'name'=>$row->name,
|
| 183 | + 'query'=>$row->query,
|
| 184 | + 'enable'=>$row->enable,
|
| 185 | + 'rep_all'=>$row->rep_all,
|
| 186 | + 'show_all'=>$row->show_all);
|
| 187 | + }
|
| 188 | + }
|
| 189 | + $db->freeResult($res);
|
| 190 | + wfProfileOut( $fname );
|
| 191 | + return $result;
|
| 192 | + }
|
| 193 | + public function getNotifyMe($notify_ids){
|
| 194 | + $fname = 'NotifyMe::getNotifyMe';
|
| 195 | + wfProfileIn( $fname );
|
| 196 | +
|
| 197 | + $result = array();
|
| 198 | +
|
| 199 | + $db = wfGetDB( DB_SLAVE );
|
| 200 | + $res = $db->select( $db->tableName('smw_nm_query'),
|
| 201 | + array('notify_id', 'name', 'query', 'show_all'),
|
| 202 | + array('notify_id'=>$notify_ids), $fname);
|
| 203 | + if($db->numRows( $res ) > 0) {
|
| 204 | + while($row = $db->fetchObject($res)) {
|
| 205 | + $result[$row->notify_id] = array('query'=>$row->query,
|
| 206 | + 'name'=>$row->name,
|
| 207 | + 'show_all'=>$row->show_all);
|
| 208 | + }
|
| 209 | + }
|
| 210 | + $db->freeResult($res);
|
| 211 | + wfProfileOut( $fname );
|
| 212 | + return $result;
|
| 213 | + }
|
| 214 | + public function getAllNotifications(){
|
| 215 | + $fname = 'NotifyMe::getAllNotifications';
|
| 216 | + wfProfileIn( $fname );
|
| 217 | +
|
| 218 | + $result = array();
|
| 219 | +
|
| 220 | + $db = wfGetDB( DB_SLAVE );
|
| 221 | + $res = $db->select( $db->tableName('smw_nm_query'),
|
| 222 | + array('notify_id', 'delegate', 'name', 'query', 'enable', 'rep_all', 'show_all'), '',
|
| 223 | + $fname, array('ORDER BY' => 'notify_id'));
|
| 224 | + if($db->numRows( $res ) > 0) {
|
| 225 | + while($row = $db->fetchObject($res)) {
|
| 226 | + $result[] = array('notify_id'=>$row->notify_id,
|
| 227 | + 'delegate'=>$row->delegate,
|
| 228 | + 'name'=>$row->name,
|
| 229 | + 'query'=>$row->query,
|
| 230 | + 'enable'=>$row->enable,
|
| 231 | + 'rep_all'=>$row->rep_all,
|
| 232 | + 'show_all'=>$row->show_all);
|
| 233 | + }
|
| 234 | + }
|
| 235 | + $db->freeResult($res);
|
| 236 | + wfProfileOut( $fname );
|
| 237 | + return $result;
|
| 238 | + }
|
| 239 | + public function disableNotifyState($notify_id){
|
| 240 | + $fname = 'NotifyMe::disableState';
|
| 241 | + wfProfileIn( $fname );
|
| 242 | + $db =& wfGetDB( DB_MASTER );
|
| 243 | + $db->delete( $db->tableName('smw_nm_monitor'), array('notify_id'=>$notify_id), $fname);
|
| 244 | + $db->delete( $db->tableName('smw_nm_relations'), array('notify_id'=>$notify_id), $fname);
|
| 245 | +
|
| 246 | + $this->updateNotifyState($notify_id, 0);
|
| 247 | + wfProfileOut( $fname );
|
| 248 | + return true;
|
| 249 | + }
|
| 250 | + public function updateNotifyState($notify_id, $state) {
|
| 251 | + $fname = 'NotifyMe::updateNotifyState';
|
| 252 | + wfProfileIn( $fname );
|
| 253 | +
|
| 254 | + $dbw =& wfGetDB( DB_MASTER );
|
| 255 | + $dbw->update( 'smw_nm_query', array('enable' => $state), array('notify_id'=>$notify_id), $fname);
|
| 256 | + wfProfileOut( $fname );
|
| 257 | + return true;
|
| 258 | + }
|
| 259 | + public function updateDelegate($notify_id, $delegate){
|
| 260 | + $fname = 'NotifyMe::updateDelegate';
|
| 261 | + wfProfileIn( $fname );
|
| 262 | + $dbw =& wfGetDB( DB_MASTER );
|
| 263 | + $dbw->update( 'smw_nm_query', array('delegate' => $delegate), array('notify_id'=>$notify_id), $fname);
|
| 264 | + wfProfileOut( $fname );
|
| 265 | + return true;
|
| 266 | + }
|
| 267 | + public function updateNotifyReportAll($notify_id, $rep_all) {
|
| 268 | + $fname = 'NotifyMe::updateNotifyReportAll';
|
| 269 | + wfProfileIn( $fname );
|
| 270 | +
|
| 271 | + $dbw =& wfGetDB( DB_MASTER );
|
| 272 | + $dbw->update( 'smw_nm_query', array('rep_all' => $rep_all), array('notify_id'=>$notify_id), $fname);
|
| 273 | + wfProfileOut( $fname );
|
| 274 | + return true;
|
| 275 | + }
|
| 276 | + public function updateNotifyShowAll($notify_id, $show_all) {
|
| 277 | + $fname = 'NotifyMe::updateNotifyShowAll';
|
| 278 | + wfProfileIn( $fname );
|
| 279 | +
|
| 280 | + $dbw =& wfGetDB( DB_MASTER );
|
| 281 | + $dbw->update( 'smw_nm_query', array('show_all' => $show_all), array('notify_id'=>$notify_id), $fname);
|
| 282 | + wfProfileOut( $fname );
|
| 283 | + return true;
|
| 284 | + }
|
| 285 | +
|
| 286 | + public function lookupSmwId($namespace, $title) {
|
| 287 | + $fname = 'NotifyMe::lookupSmwId';
|
| 288 | + wfProfileIn( $fname );
|
| 289 | +
|
| 290 | + $result = 0;
|
| 291 | + $db = wfGetDB( DB_SLAVE );
|
| 292 | + $res = $db->selectRow( 'smw_ids', 'smw_id', array( 'smw_namespace' => $namespace, 'smw_title' => $title ), $fname );
|
| 293 | +
|
| 294 | + if($res) $result = $res->smw_id;
|
| 295 | + wfProfileOut( $fname );
|
| 296 | + return $result;
|
| 297 | + }
|
| 298 | + public function getMonitoredNotifications($page_id) {
|
| 299 | + $fname = 'NotifyMe::getMonitoredNotifications';
|
| 300 | + wfProfileIn( $fname );
|
| 301 | +
|
| 302 | + $result = array();
|
| 303 | +
|
| 304 | + $db = wfGetDB( DB_SLAVE );
|
| 305 | +
|
| 306 | + $res = $db->select( array($db->tableName('smw_nm_query').' q', $db->tableName('smw_nm_monitor').' m'),
|
| 307 | + array('q.user_id', 'q.notify_id', 'q.delegate', 'q.name', 'q.rep_all'),
|
| 308 | + 'm.page_id='.$page_id.' and m.notify_id=q.notify_id', $fname);
|
| 309 | + if($db->numRows( $res ) > 0) {
|
| 310 | + while($row = $db->fetchObject($res)) {
|
| 311 | + $ds = explode(',', $row->delegate);
|
| 312 | + $delegated = false;
|
| 313 | + foreach($ds as $delegate) {
|
| 314 | + $u = User::newFromName(trim($delegate));
|
| 315 | + if($u==null) continue;
|
| 316 | + $id = $u->getId();
|
| 317 | + if($id > 0) {
|
| 318 | + $result[$id][$row->notify_id] = array('name'=>$row->name, 'rep_all'=>$row->rep_all);
|
| 319 | + $delegated = true;
|
| 320 | + }
|
| 321 | + }
|
| 322 | + if(!$delegated) {
|
| 323 | + $result[$row->user_id][$row->notify_id] = array('name'=>$row->name, 'rep_all'=>$row->rep_all);
|
| 324 | + }
|
| 325 | + }
|
| 326 | + }
|
| 327 | + $db->freeResult($res);
|
| 328 | +
|
| 329 | + wfProfileOut( $fname );
|
| 330 | + return $result;
|
| 331 | + }
|
| 332 | + public function getMonitoredNotificationsDetail($page_id) {
|
| 333 | + $fname = 'NotifyMe::getMonitoredNotificationsDetail';
|
| 334 | + wfProfileIn( $fname );
|
| 335 | +
|
| 336 | + $result = array();
|
| 337 | +
|
| 338 | + $notifies = $this->getMonitoredNotifications($page_id);
|
| 339 | +
|
| 340 | + $db = wfGetDB( DB_SLAVE );
|
| 341 | +
|
| 342 | + foreach ($notifies as $user_id=>$notify) {
|
| 343 | + foreach($notify as $notify_id=>$notify_detail) {
|
| 344 | + if($notify_detail['rep_all']==1) {
|
| 345 | + $result[$user_id]['rep_all'][$notify_id] = $notify_detail['name'];
|
| 346 | + } else {
|
| 347 | + $res = $db->select( $db->tableName('smw_nm_relations'), array('smw_id', 'type'), "notify_id=$notify_id AND subquery=0", $fname);
|
| 348 | + if($db->numRows( $res ) > 0) {
|
| 349 | + while($row = $db->fetchObject($res)) {
|
| 350 | + $result[$user_id]['semantic'][SMWNotifyProcessor::toInfoId($row->type,0,$row->smw_id)][$notify_id] = $notify_detail['name'];
|
| 351 | + }
|
| 352 | + }
|
| 353 | + $db->freeResult($res);
|
| 354 | + }
|
| 355 | + }
|
| 356 | + }
|
| 357 | +
|
| 358 | + wfProfileOut( $fname );
|
| 359 | + return $result;
|
| 360 | + }
|
| 361 | + public function getPossibleQuery($info) {
|
| 362 | + $cnt = count($info);
|
| 363 | + if($cnt == 0) {
|
| 364 | + return null;
|
| 365 | + }
|
| 366 | + $fname = 'NotifyMe::getPossibleQuery';
|
| 367 | + wfProfileIn( $fname );
|
| 368 | +
|
| 369 | + $first = true;
|
| 370 | + foreach($info as $key=>$value) {
|
| 371 | + if($first) {
|
| 372 | + $first = false;
|
| 373 | + } else {
|
| 374 | + $cond .= "OR ";
|
| 375 | + }
|
| 376 | + $i = SMWNotifyProcessor::getInfoFromId($key);
|
| 377 | + $cond .= "(smw_id=$i[attr_id] AND type=$i[type]) ";
|
| 378 | + }
|
| 379 | +
|
| 380 | + $db =& wfGetDB( DB_SLAVE );
|
| 381 | + $result = array(0=>array(), 1=>array());
|
| 382 | + extract( $db->tableNames('smw_nm_query', 'smw_nm_relations') );
|
| 383 | + $res = $db->query("SELECT q.notify_id, q.name, q.user_id, q.delegate, q.nm_sql, q.nm_hierarchy, c1.subquery FROM (".
|
| 384 | + "SELECT count(*) cnt, notify_id, subquery FROM $smw_nm_relations ".
|
| 385 | + "WHERE $cond GROUP BY notify_id, subquery".
|
| 386 | + ") c1 INNER JOIN (".
|
| 387 | + "SELECT count(*) cnt, notify_id, subquery FROM $smw_nm_relations ".
|
| 388 | + "WHERE type<>1 GROUP BY notify_id, subquery".
|
| 389 | + ") c2 ON c1.notify_id=c2.notify_id AND c1.subquery=c2.subquery AND c1.cnt=c2.cnt ".
|
| 390 | + "LEFT JOIN $smw_nm_query q ON q.notify_id = c1.notify_id WHERE c1.cnt<=$cnt", $fname);
|
| 391 | + if($db->numRows( $res ) > 0) {
|
| 392 | + while($row = $db->fetchObject($res)) {
|
| 393 | + $ds = explode(',', $row->delegate);
|
| 394 | + $delegated = false;
|
| 395 | + foreach($ds as $delegate) {
|
| 396 | + $u = User::newFromName(trim($delegate));
|
| 397 | + if($u==null) continue;
|
| 398 | + $id = $u->getId();
|
| 399 | + if($id > 0) {
|
| 400 | + $result[$row->subquery>0?1:0][$row->notify_id] = array('user_id'=>$id, 'name'=>$row->name, 'sql'=>$row->nm_sql, 'hierarchy'=>$row->nm_hierarchy);
|
| 401 | + $delegated = true;
|
| 402 | + }
|
| 403 | + }
|
| 404 | + if(!$delegated) {
|
| 405 | + $result[$row->subquery>0?1:0][$row->notify_id] = array('user_id'=>$row->user_id, 'name'=>$row->name, 'sql'=>$row->nm_sql, 'hierarchy'=>$row->nm_hierarchy);
|
| 406 | + }
|
| 407 | + }
|
| 408 | + }
|
| 409 | + $db->freeResult($res);
|
| 410 | + wfProfileOut( $fname );
|
| 411 | + return $result;
|
| 412 | + }
|
| 413 | + public function getMonitoredQuery($page_id) {
|
| 414 | + $fname = 'NotifyMe::getMonitoredQuery';
|
| 415 | + wfProfileIn( $fname );
|
| 416 | +
|
| 417 | + $db =& wfGetDB( DB_SLAVE );
|
| 418 | + $res = $db->select( array($db->tableName('smw_nm_monitor').' m', $db->tableName('smw_nm_query').' q'),
|
| 419 | + array('q.notify_id, q.delegate, q.name, q.user_id, q.nm_sql, q.nm_hierarchy'),
|
| 420 | + "m.notify_id=q.notify_id AND m.page_id=$page_id", $fname);
|
| 421 | + if($db->numRows( $res ) > 0) {
|
| 422 | + while($row = $db->fetchObject($res)) {
|
| 423 | + $ds = explode(',', $row->delegate);
|
| 424 | + $delegated = false;
|
| 425 | + foreach($ds as $delegate) {
|
| 426 | + $u = User::newFromName(trim($delegate));
|
| 427 | + if($u==null) continue;
|
| 428 | + $id = $u->getId();
|
| 429 | + if($id > 0) {
|
| 430 | + $result[$row->notify_id] = array('user_id'=>$id, 'name'=>$row->name, 'sql'=>$row->nm_sql, 'hierarchy'=>$row->nm_hierarchy);
|
| 431 | + $delegated = true;
|
| 432 | + }
|
| 433 | + }
|
| 434 | + if(!$delegated) {
|
| 435 | + $result[$row->notify_id] = array('user_id'=>$row->user_id, 'name'=>$row->name, 'sql'=>$row->nm_sql, 'hierarchy'=>$row->nm_hierarchy);
|
| 436 | + }
|
| 437 | + }
|
| 438 | + }
|
| 439 | + $db->freeResult($res);
|
| 440 | + wfProfileOut( $fname );
|
| 441 | + return $result;
|
| 442 | + }
|
| 443 | + public function updateNMSql($notify_id, $sql, $tmp_hierarchy) {
|
| 444 | + $fname = 'NotifyMe::updateNMSql';
|
| 445 | + wfProfileIn( $fname );
|
| 446 | +
|
| 447 | + $dbw =& wfGetDB( DB_MASTER );
|
| 448 | + $dbw->update( 'smw_nm_query', array('nm_sql' => $sql, 'nm_hierarchy' => $tmp_hierarchy), array('notify_id'=>$notify_id), $fname);
|
| 449 | + wfProfileOut( $fname );
|
| 450 | + return true;
|
| 451 | + }
|
| 452 | + public function getNotifyInMainQuery($page_id, $notify_id, $sql, $hierarchy, &$match, &$monitoring) {
|
| 453 | + $fname = 'NotifyMe::getNotifyInMainQuery';
|
| 454 | + wfProfileIn( $fname );
|
| 455 | +
|
| 456 | + $db =& wfGetDB( DB_SLAVE );
|
| 457 | +
|
| 458 | + $tmp_tables = array();
|
| 459 | + if($hierarchy != '') {
|
| 460 | + $_hierarchies = array();
|
| 461 | + foreach(explode(";",$hierarchy) as $attrs) {
|
| 462 | + $part = explode(":",$attrs,3);
|
| 463 | + $tablename = $db->tableName($part[0]);
|
| 464 | + $depth = intval($part[1]);
|
| 465 | + $values = $part[2];
|
| 466 | +
|
| 467 | + $tmp_tables[] = $tablename;
|
| 468 | +
|
| 469 | + $db->query( "CREATE TEMPORARY TABLE $tablename ( id INT UNSIGNED NOT NULL KEY) TYPE=MEMORY", 'SMW::executeQueries' );
|
| 470 | + if (array_key_exists($values, $_hierarchies)) { // just copy known result
|
| 471 | + $db->query("INSERT INTO $tablename (id) SELECT id FROM " . $_hierarchies[$values], 'SMW::executeQueries');
|
| 472 | + } else {
|
| 473 | + $tmpnew = 'smw_new';
|
| 474 | + $tmpres = 'smw_res';
|
| 475 | + $db->query( "CREATE TEMPORARY TABLE $tmpnew ( id INT UNSIGNED ) TYPE=MEMORY", 'SMW::executeQueries' );
|
| 476 | + $db->query( "CREATE TEMPORARY TABLE $tmpres ( id INT UNSIGNED ) TYPE=MEMORY", 'SMW::executeQueries' );
|
| 477 | + $db->query("INSERT IGNORE INTO $tablename (id) VALUES $values", 'SMW::executeQueries');
|
| 478 | + $db->query("INSERT IGNORE INTO $tmpnew (id) VALUES $values", 'SMW::executeQueries');
|
| 479 | + $smw_subs2 = $db->tableName('smw_subs2');
|
| 480 | + for ($i=0; $i<$depth; $i++) {
|
| 481 | + $db->query("INSERT INTO $tmpres (id) SELECT s_id FROM $smw_subs2, $tmpnew WHERE o_id=id", 'SMW::executeQueries');
|
| 482 | + if ($db->affectedRows() == 0) { // no change, exit loop
|
| 483 | + break;
|
| 484 | + }
|
| 485 | + $db->query("INSERT IGNORE INTO $tablename (id) SELECT $tmpres.id FROM $tmpres", 'SMW::executeQueries');
|
| 486 | + if ($db->affectedRows() == 0) { // no change, exit loop
|
| 487 | + break;
|
| 488 | + }
|
| 489 | + $db->query('TRUNCATE TABLE ' . $tmpnew, 'SMW::executeQueries'); // empty "new" table
|
| 490 | + $tmpname = $tmpnew;
|
| 491 | + $tmpnew = $tmpres;
|
| 492 | + $tmpres = $tmpname;
|
| 493 | + }
|
| 494 | + $_hierarchies[$values] = $tablename;
|
| 495 | + $db->query('DROP TEMPORARY TABLE smw_new', 'SMW::executeQueries');
|
| 496 | + $db->query('DROP TEMPORARY TABLE smw_res', 'SMW::executeQueries');
|
| 497 | + }
|
| 498 | + }
|
| 499 | + }
|
| 500 | +
|
| 501 | + $sql = "SELECT p.page_id FROM " . $db->tableName('page') . " AS p INNER JOIN ($sql) AS s ON s.t=p.page_title AND s.ns=p.page_namespace WHERE p.page_id=$page_id";
|
| 502 | + $res = $db->query($sql, $fname);
|
| 503 | + $match = ($db->numRows( $res ) > 0);
|
| 504 | + $db->freeResult($res);
|
| 505 | +
|
| 506 | + foreach ($tmp_tables as $tablename) {
|
| 507 | + $db->query("DROP TEMPORARY TABLE $tablename", 'SMW::getQueryResult');
|
| 508 | + }
|
| 509 | +
|
| 510 | + $monitoring = ($db->selectRow( 'smw_nm_monitor', array( 'page_id' ),
|
| 511 | + array( 'page_id' => $page_id, 'notify_id' => $notify_id ), $fname ) != false);
|
| 512 | +
|
| 513 | + wfProfileOut( $fname );
|
| 514 | + return true;
|
| 515 | + }
|
| 516 | + public function getNotifyInSubquery($notify_id, $sql, $hierarchy) {
|
| 517 | + $fname = 'NotifyMe::getNotifyInSubquery';
|
| 518 | + wfProfileIn( $fname );
|
| 519 | +
|
| 520 | + $result = array('monitoring'=>array(), 'match'=>array());
|
| 521 | +
|
| 522 | + $db =& wfGetDB( DB_SLAVE );
|
| 523 | +
|
| 524 | + $tmp_tables = array();
|
| 525 | + if($hierarchy != '') {
|
| 526 | + $_hierarchies = array();
|
| 527 | + foreach(explode(";",$hierarchy) as $attrs) {
|
| 528 | + $part = explode(":",$attrs,3);
|
| 529 | + $tablename = $db->tableName($part[0]);
|
| 530 | + $depth = intval($part[1]);
|
| 531 | + $values = $part[2];
|
| 532 | +
|
| 533 | + $tmp_tables[] = $tablename;
|
| 534 | +
|
| 535 | + $db->query( "CREATE TEMPORARY TABLE $tablename ( id INT UNSIGNED NOT NULL KEY) TYPE=MEMORY", 'SMW::executeQueries' );
|
| 536 | + if (array_key_exists($values, $_hierarchies)) { // just copy known result
|
| 537 | + $db->query("INSERT INTO $tablename (id) SELECT id FROM " . $_hierarchies[$values], 'SMW::executeQueries');
|
| 538 | + } else {
|
| 539 | + $tmpnew = 'smw_new';
|
| 540 | + $tmpres = 'smw_res';
|
| 541 | + $db->query( "CREATE TEMPORARY TABLE $tmpnew ( id INT UNSIGNED ) TYPE=MEMORY", 'SMW::executeQueries' );
|
| 542 | + $db->query( "CREATE TEMPORARY TABLE $tmpres ( id INT UNSIGNED ) TYPE=MEMORY", 'SMW::executeQueries' );
|
| 543 | + $db->query("INSERT IGNORE INTO $tablename (id) VALUES $values", 'SMW::executeQueries');
|
| 544 | + $db->query("INSERT IGNORE INTO $tmpnew (id) VALUES $values", 'SMW::executeQueries');
|
| 545 | + $smw_subs2 = $db->tableName('smw_subs2');
|
| 546 | + for ($i=0; $i<$depth; $i++) {
|
| 547 | + $db->query("INSERT INTO $tmpres (id) SELECT s_id FROM $smw_subs2, $tmpnew WHERE o_id=id", 'SMW::executeQueries');
|
| 548 | + if ($db->affectedRows() == 0) { // no change, exit loop
|
| 549 | + break;
|
| 550 | + }
|
| 551 | + $db->query("INSERT IGNORE INTO $tablename (id) SELECT $tmpres.id FROM $tmpres", 'SMW::executeQueries');
|
| 552 | + if ($db->affectedRows() == 0) { // no change, exit loop
|
| 553 | + break;
|
| 554 | + }
|
| 555 | + $db->query('TRUNCATE TABLE ' . $tmpnew, 'SMW::executeQueries'); // empty "new" table
|
| 556 | + $tmpname = $tmpnew;
|
| 557 | + $tmpnew = $tmpres;
|
| 558 | + $tmpres = $tmpname;
|
| 559 | + }
|
| 560 | + $_hierarchies[$values] = $tablename;
|
| 561 | + $db->query('DROP TEMPORARY TABLE smw_new', 'SMW::executeQueries');
|
| 562 | + $db->query('DROP TEMPORARY TABLE smw_res', 'SMW::executeQueries');
|
| 563 | + }
|
| 564 | + }
|
| 565 | + }
|
| 566 | +
|
| 567 | + $res = $db->query("SELECT p.page_id FROM " . $db->tableName('page') . " AS p INNER JOIN ($sql) AS s ON s.t=p.page_title AND s.ns=p.page_namespace", $fname);
|
| 568 | + if($db->numRows( $res ) > 0) {
|
| 569 | + while($row = $db->fetchObject($res)) {
|
| 570 | + $result['match'][] = $row->page_id;
|
| 571 | + }
|
| 572 | + }
|
| 573 | + $db->freeResult($res);
|
| 574 | +
|
| 575 | + foreach ($tmp_tables as $tablename) {
|
| 576 | + $db->query("DROP TEMPORARY TABLE $tablename", 'SMW::getQueryResult');
|
| 577 | + }
|
| 578 | +
|
| 579 | + $res = $db->select( $db->tableName('smw_nm_monitor'), array( 'page_id' ), array( 'notify_id' => $notify_id ), $fname );
|
| 580 | + if($db->numRows( $res ) > 0) {
|
| 581 | + while($row = $db->fetchObject($res)) {
|
| 582 | + $result['monitoring'][] = $row->page_id;
|
| 583 | + }
|
| 584 | + }
|
| 585 | + $db->freeResult($res);
|
| 586 | +
|
| 587 | + wfProfileOut( $fname );
|
| 588 | + return $result;
|
| 589 | + }
|
| 590 | + public function getUserInfo($user_id) {
|
| 591 | + $fname = 'NotifyMe::getUserInfo';
|
| 592 | + wfProfileIn( $fname );
|
| 593 | +
|
| 594 | + $db = wfGetDB( DB_SLAVE );
|
| 595 | + $result = $db->selectRow( 'user', array( 'user_name', 'user_real_name', 'user_email', 'user_options' ), array( 'user_id' => $user_id ), $fname );
|
| 596 | +
|
| 597 | + wfProfileOut( $fname );
|
| 598 | + return $result;
|
| 599 | + }
|
| 600 | + public function getPageTitle($page_id) {
|
| 601 | + $db =& wfGetDB( DB_SLAVE );
|
| 602 | + return $db->selectRow('page', 'page_title', array('page_id' => $page_id));
|
| 603 | + }
|
| 604 | + public function addNotifyRSS($type, $id, $title, $msg, $link = NULL) {
|
| 605 | + $fname = 'NotifyMe::addNotifyRSS';
|
| 606 | + wfProfileIn( $fname );
|
| 607 | + $db =& wfGetDB( DB_MASTER );
|
| 608 | +
|
| 609 | + $rid = $db->nextSequenceValue('nmrss_msg_id_seq');
|
| 610 | + $values = array('msg_id' => $rid,
|
| 611 | + 'title' => $title,
|
| 612 | + 'notify' => $msg,
|
| 613 | + 'timestamp' => $db->timestamp());
|
| 614 | + if($type == "uid") {
|
| 615 | + $values['user_id'] = $id;
|
| 616 | + } else {
|
| 617 | + $values['notify_id'] = $id;
|
| 618 | + }
|
| 619 | + if($link !== NULL) {
|
| 620 | + $values['link'] = $link;
|
| 621 | + }
|
| 622 | +
|
| 623 | + $db->insert( 'smw_nm_rss', $values, $fname);
|
| 624 | + $id = $db->insertId();
|
| 625 | +
|
| 626 | + wfProfileOut( $fname );
|
| 627 | + return $id;
|
| 628 | + }
|
| 629 | + public function getNotifyRSS($type, $id, $limit) {
|
| 630 | + $fname = 'NotifyMe::getNotifyRSS';
|
| 631 | + wfProfileIn( $fname );
|
| 632 | + $db =& wfGetDB( DB_SLAVE );
|
| 633 | + $result = array();
|
| 634 | + if($type == "uid") {
|
| 635 | + $ids = array('user_id'=>$id);
|
| 636 | + } else {
|
| 637 | + $ids = array('notify_id'=>$id);
|
| 638 | + }
|
| 639 | + $res = $db->select( $db->tableName('smw_nm_rss'), array('title', 'link', 'notify', 'timestamp'), $ids, $fname, array('ORDER BY' => 'timestamp DESC', 'LIMIT'=>$limit));
|
| 640 | + if($db->numRows( $res ) > 0) {
|
| 641 | + while($row = $db->fetchObject($res)) {
|
| 642 | + $result[] = array('title' => $row->title, 'link'=>$row->link, 'notify'=>$row->notify, 'timestamp'=>$row->timestamp);
|
| 643 | + }
|
| 644 | + }
|
| 645 | + $db->freeResult($res);
|
| 646 | + wfProfileOut( $fname );
|
| 647 | + return $result;
|
| 648 | + }
|
| 649 | + public function getUnmailedNMMessages() {
|
| 650 | + $fname = 'NotifyMe::getUnmailedNMMessages';
|
| 651 | + wfProfileIn( $fname );
|
| 652 | + $db =& wfGetDB( DB_MASTER );
|
| 653 | + $smw_nm_rss = $db->tableName('smw_nm_rss');
|
| 654 | + $result = array();
|
| 655 | + $res = $db->select( $smw_nm_rss,
|
| 656 | + array('user_id', 'title', 'notify', 'timestamp'),
|
| 657 | + array('mailed'=>'0'), $fname);
|
| 658 | + if($db->numRows( $res ) > 0) {
|
| 659 | + while($row = $db->fetchObject($res)) {
|
| 660 | + $result[] = array('user_id' => $row->user_id, 'title' => $row->title, 'notify'=>$row->notify, 'timestamp'=>$row->timestamp);
|
| 661 | + }
|
| 662 | + }
|
| 663 | + $db->freeResult($res);
|
| 664 | + $db->update($smw_nm_rss, array('mailed' => '1'), array('mailed' => '0'), $fname);
|
| 665 | + wfProfileOut( $fname );
|
| 666 | + return $result;
|
| 667 | + }
|
| 668 | +
|
| 669 | + public function getGroupedUsers($groups) {
|
| 670 | + $fname = 'NotifyMe::getGroupedUsers';
|
| 671 | + wfProfileIn( $fname );
|
| 672 | + $db =& wfGetDB( DB_SLAVE );
|
| 673 | + extract( $db->tableNames('user_groups', 'user') );
|
| 674 | + $result = array();
|
| 675 | + $res = $db->query( "SELECT u.user_name FROM $user u".
|
| 676 | + ($groups ? " LEFT JOIN $user_groups g ON u.user_id = g.ug_user
|
| 677 | + WHERE g.ug_group IN ('".join("','", $groups)."')
|
| 678 | + GROUP BY user_name":""), $fname);
|
| 679 | + if($db->numRows( $res ) > 0) {
|
| 680 | + while($row = $db->fetchObject($res)) {
|
| 681 | + $result[] = $row->user_name;
|
| 682 | + }
|
| 683 | + }
|
| 684 | + $db->freeResult($res);
|
| 685 | + wfProfileOut( $fname );
|
| 686 | + return $result;
|
| 687 | + }
|
| 688 | +
|
| 689 | + function getNMQueryResult(SMWQuery $query) {
|
| 690 | + wfProfileIn('SMWSQLStore2::getNMQueryResult (SMW)');
|
| 691 | + global $smwgNMIP;
|
| 692 | + include_once("$smwgNMIP/includes/storage/SMW_SQLStore2_QueriesNM.php");
|
| 693 | + $qe = new SMWSQLStore2QueryEngineNM(smwfGetStore(), wfGetDB( DB_SLAVE ));
|
| 694 | + $result = $qe->getQueryResult($query);
|
| 695 | + wfProfileOut('SMWSQLStore2::getNMQueryResult (SMW)');
|
| 696 | + return $result;
|
| 697 | + }
|
| 698 | +
|
| 699 | +}
|
| 700 | +
|
| 701 | +?> |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/includes/SMW_NMStorage.php |
— | — | @@ -0,0 +1,84 @@ |
| 2 | +<?php
|
| 3 | +
|
| 4 | +/**
|
| 5 | + * This file provides the access to the database tables that are
|
| 6 | + * used by the NotifyMe extension.
|
| 7 | + *
|
| 8 | + * @author dch
|
| 9 | + *
|
| 10 | + */
|
| 11 | +if ( !defined( 'MEDIAWIKI' ) ) die;
|
| 12 | +global $smwgNMIP;
|
| 13 | +require_once $smwgNMIP . '/includes/SMW_NMDBHelper.php';
|
| 14 | +
|
| 15 | +
|
| 16 | +
|
| 17 | +/**
|
| 18 | + * This class encapsulates all methods that care about the database tables of
|
| 19 | + * the NotifyMe extension. It is a singleton that contains an instance
|
| 20 | + * of the actual database access object e.g. the Mediawiki SQL database.
|
| 21 | + *
|
| 22 | + */
|
| 23 | +class NMStorage {
|
| 24 | +
|
| 25 | + //--- Private fields---
|
| 26 | +
|
| 27 | + private static $mInstance; // NMStorage: the only instance of this singleton
|
| 28 | + private static $mDatabase; // The actual database object
|
| 29 | +
|
| 30 | + //--- Constructor ---
|
| 31 | +
|
| 32 | + /**
|
| 33 | + * Constructor.
|
| 34 | + * Creates the object that handles the concrete database access.
|
| 35 | + *
|
| 36 | + */
|
| 37 | + private function __construct() {
|
| 38 | + if (self::$mDatabase == NULL) {
|
| 39 | + global $smwgBaseStore;
|
| 40 | + switch ($smwgBaseStore) {
|
| 41 | + case (SMW_STORE_TESTING):
|
| 42 | + trigger_error('Testing store not implemented for NotifyMe extension.');
|
| 43 | + break;
|
| 44 | + case (SMW_STORE_MWDB):
|
| 45 | + default:
|
| 46 | + global $smwgNMIP;
|
| 47 | + require_once($smwgNMIP . '/includes/storage/SMW_NMStorageSQL.php');
|
| 48 | + self::$mDatabase = new NMStorageSQL();
|
| 49 | + break;
|
| 50 | + }
|
| 51 | + }
|
| 52 | +
|
| 53 | + }
|
| 54 | +
|
| 55 | + //--- Public methods ---
|
| 56 | +
|
| 57 | + /**
|
| 58 | + * Returns the single instance of this class.
|
| 59 | + *
|
| 60 | + * @return NMStorage
|
| 61 | + * The single instance of this class.
|
| 62 | + */
|
| 63 | + public static function getInstance() {
|
| 64 | + if (!isset(self::$mInstance)) {
|
| 65 | + $c = __CLASS__;
|
| 66 | + self::$mInstance = new $c;
|
| 67 | + }
|
| 68 | +
|
| 69 | + return self::$mInstance;
|
| 70 | + }
|
| 71 | +
|
| 72 | + /**
|
| 73 | + * Returns the actual database.
|
| 74 | + *
|
| 75 | + * @return object
|
| 76 | + * The object to access the database.
|
| 77 | + */
|
| 78 | + public static function getDatabase() {
|
| 79 | + self::getInstance(); // Make sure, singleton is initialized
|
| 80 | + return self::$mDatabase;
|
| 81 | + }
|
| 82 | +
|
| 83 | +}
|
| 84 | +
|
| 85 | +?> |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/includes/SMW_NMDBHelper.php |
— | — | @@ -0,0 +1,304 @@ |
| 2 | +<?php
|
| 3 | +/*
|
| 4 | + * Created on 19.10.2008
|
| 5 | + *
|
| 6 | + * Author: dch
|
| 7 | + */
|
| 8 | +
|
| 9 | +
|
| 10 | +
|
| 11 | +class SNMDBHelper {
|
| 12 | +
|
| 13 | +
|
| 14 | + /**
|
| 15 | + * Make sure the table of the given name has the given fields, provided
|
| 16 | + * as an array with entries fieldname => typeparams. typeparams should be
|
| 17 | + * in a normalised form and order to match to existing values.
|
| 18 | + *
|
| 19 | + * The function returns an array that includes all columns that have been
|
| 20 | + * changed. For each such column, the array contains an entry
|
| 21 | + * columnname => action, where action is one of 'up', 'new', or 'del'
|
| 22 | + * If the table was already fine or was created completely anew, an empty
|
| 23 | + * array is returned (assuming that both cases require no action).
|
| 24 | + *
|
| 25 | + * NOTE: the function partly ignores the order in which fields are set up.
|
| 26 | + * Only if the type of some field changes will its order be adjusted explicitly.
|
| 27 | + *
|
| 28 | + * @param string $primaryKeys
|
| 29 | + * This optional string specifies the primary keys if there is more
|
| 30 | + * than one. This is a comma separated list of column names. The primary
|
| 31 | + * keys are not altered, if the table already exists.
|
| 32 | + */
|
| 33 | + public static function setupTable($table, $fields, $db, $verbose, $primaryKeys = "") {
|
| 34 | + global $wgDBname;
|
| 35 | + SNMDBHelper::reportProgress("Setting up table $table ...\n",$verbose);
|
| 36 | + if ($db->tableExists($table) === false) { // create new table
|
| 37 | + $sql = 'CREATE TABLE ' . $wgDBname . '.' . $table . ' (';
|
| 38 | + $first = true;
|
| 39 | + foreach ($fields as $name => $type) {
|
| 40 | + if ($first) {
|
| 41 | + $first = false;
|
| 42 | + } else {
|
| 43 | + $sql .= ',';
|
| 44 | + }
|
| 45 | + $sql .= $name . ' ' . $type;
|
| 46 | + }
|
| 47 | + if (!empty($primaryKeys)) {
|
| 48 | + $sql .= ", PRIMARY KEY(".$primaryKeys.")";
|
| 49 | + }
|
| 50 | + $sql .= ') TYPE=myisam';
|
| 51 | + $db->query( $sql, 'SNMDBHelper::setupTable' );
|
| 52 | + SNMDBHelper::reportProgress(" ... new table created\n",$verbose);
|
| 53 | + return array();
|
| 54 | + } else { // check table signature
|
| 55 | + SNMDBHelper::reportProgress(" ... table exists already, checking structure ...\n",$verbose);
|
| 56 | + $res = $db->query( 'DESCRIBE ' . $table, 'SNMDBHelper::setupTable' );
|
| 57 | + $curfields = array();
|
| 58 | + $result = array();
|
| 59 | + while ($row = $db->fetchObject($res)) {
|
| 60 | + $type = strtoupper($row->Type);
|
| 61 | + if (substr($type,0,8) == 'VARCHAR(') {
|
| 62 | + $type .= ' binary'; // just assume this to be the case for VARCHAR, avoid collation checks
|
| 63 | + }
|
| 64 | + if ($row->Null != 'YES') {
|
| 65 | + $type .= ' NOT NULL';
|
| 66 | + }
|
| 67 | + if ($row->Key == 'PRI') { /// FIXME: updating "KEY" is not possible, the below query will fail in this case.
|
| 68 | + $type .= ' KEY';
|
| 69 | + }
|
| 70 | + if ($row->Extra == 'auto_increment') {
|
| 71 | + $type .= ' AUTO_INCREMENT';
|
| 72 | + }
|
| 73 | + if ($row->Default != '') {
|
| 74 | + $type .= ' default \''.$row->Default.'\'';
|
| 75 | + }
|
| 76 | +
|
| 77 | + $curfields[$row->Field] = $type;
|
| 78 | + }
|
| 79 | + $position = 'FIRST';
|
| 80 | + foreach ($fields as $name => $type) {
|
| 81 | + if ( !array_key_exists($name,$curfields) ) {
|
| 82 | + SNMDBHelper::reportProgress(" ... creating column $name ... ",$verbose);
|
| 83 | + $db->query("ALTER TABLE $table ADD `$name` $type $position", 'SNMDBHelper::setupTable');
|
| 84 | + $result[$name] = 'new';
|
| 85 | + SNMDBHelper::reportProgress("done \n",$verbose);
|
| 86 | + } elseif ($curfields[$name] != $type) {// && stripos("auto_increment", $type) == -1) {
|
| 87 | + SNMDBHelper::reportProgress(" ... changing type of column $name from '$curfields[$name]' to '$type' ... ",$verbose);
|
| 88 | + $db->query("ALTER TABLE $table CHANGE `$name` `$name` $type $position", 'SNMDBHelper::setupTable');
|
| 89 | + $result[$name] = 'up';
|
| 90 | + $curfields[$name] = false;
|
| 91 | + SNMDBHelper::reportProgress("done.\n",$verbose);
|
| 92 | + } else {
|
| 93 | + SNMDBHelper::reportProgress(" ... column $name is fine\n",$verbose);
|
| 94 | + $curfields[$name] = false;
|
| 95 | + }
|
| 96 | + $position = "AFTER $name";
|
| 97 | + }
|
| 98 | + foreach ($curfields as $name => $value) {
|
| 99 | + if ($value !== false) { // not encountered yet --> delete
|
| 100 | + SNMDBHelper::reportProgress(" ... deleting obsolete column $name ... ",$verbose);
|
| 101 | + $db->query("ALTER TABLE $table DROP COLUMN `$name`", 'SNMDBHelper::setupTable');
|
| 102 | + $result[$name] = 'del';
|
| 103 | + SNMDBHelper::reportProgress("done.\n",$verbose);
|
| 104 | + }
|
| 105 | + }
|
| 106 | + SNMDBHelper::reportProgress(" ... table $table set up successfully.\n",$verbose);
|
| 107 | + return $result;
|
| 108 | + }
|
| 109 | + }
|
| 110 | +
|
| 111 | + /**
|
| 112 | + * Make sure that each of the column descriptions in the given array is indexed by *one* index
|
| 113 | + * in the given DB table.
|
| 114 | + */
|
| 115 | + public static function setupIndex($table, $columns, $db) {
|
| 116 | + $table = $db->tableName($table);
|
| 117 | + $res = $db->query( 'SHOW INDEX FROM ' . $table , 'SMW::SetupIndex');
|
| 118 | + if ( !$res ) {
|
| 119 | + return false;
|
| 120 | + }
|
| 121 | + $indexes = array();
|
| 122 | + while ( $row = $db->fetchObject( $res ) ) {
|
| 123 | + if (!array_key_exists($row->Key_name, $indexes)) {
|
| 124 | + $indexes[$row->Key_name] = array();
|
| 125 | + }
|
| 126 | + $indexes[$row->Key_name][$row->Seq_in_index] = $row->Column_name;
|
| 127 | + }
|
| 128 | + foreach ($indexes as $key => $index) { // clean up existing indexes
|
| 129 | + $id = array_search(implode(',', $index), $columns );
|
| 130 | + if ( $id !== false ) {
|
| 131 | + $columns[$id] = false;
|
| 132 | + } else { // duplicate or unrequired index
|
| 133 | + if($key != 'PRIMARY') {
|
| 134 | + $db->query( 'DROP INDEX ' . $key . ' ON ' . $table, 'SMW::SetupIndex');
|
| 135 | + }
|
| 136 | + }
|
| 137 | + }
|
| 138 | +
|
| 139 | + foreach ($columns as $column) { // add remaining indexes
|
| 140 | + if ($column != false) {
|
| 141 | + $db->query( "ALTER TABLE $table ADD INDEX ( $column )", 'SMW::SetupIndex');
|
| 142 | + }
|
| 143 | + }
|
| 144 | + return true;
|
| 145 | + }
|
| 146 | +
|
| 147 | + /**
|
| 148 | + * Print some output to indicate progress. The output message is given by
|
| 149 | + * $msg, while $verbose indicates whether or not output is desired at all.
|
| 150 | + */
|
| 151 | + public static function reportProgress($msg, $verbose) {
|
| 152 | + if (!$verbose) {
|
| 153 | + return;
|
| 154 | + }
|
| 155 | + if (ob_get_level() == 0) { // be sure to have some buffer, otherwise some PHPs complain
|
| 156 | + ob_start();
|
| 157 | + }
|
| 158 | + print $msg;
|
| 159 | + ob_flush();
|
| 160 | + flush();
|
| 161 | + }
|
| 162 | +
|
| 163 | + /**
|
| 164 | + * Transform input parameters into a suitable array of SQL options.
|
| 165 | + * The parameter $valuecol defines the string name of the column to which
|
| 166 | + * sorting requests etc. are to be applied.
|
| 167 | + */
|
| 168 | + public static function getSQLOptions($requestoptions, $valuecol = NULL) {
|
| 169 | + $sql_options = array();
|
| 170 | + if ($requestoptions !== NULL) {
|
| 171 | + if (is_numeric($requestoptions->limit) && $requestoptions->limit >= 0) {
|
| 172 | + $sql_options['LIMIT'] = $requestoptions->limit;
|
| 173 | + }
|
| 174 | + if (is_numeric($requestoptions->offset) && $requestoptions->offset > 0) {
|
| 175 | + $sql_options['OFFSET'] = $requestoptions->offset;
|
| 176 | + }
|
| 177 | + if ( ($valuecol !== NULL) && ($requestoptions->sort) ) {
|
| 178 | + if (is_array($valuecol)) {
|
| 179 | + $sql_options['ORDER BY'] = $requestoptions->ascending ? mysql_real_escape_string(implode(",",$valuecol)) : mysql_real_escape_string(implode(",",$valuecol)) . ' DESC';
|
| 180 | + } else {
|
| 181 | + $sql_options['ORDER BY'] = $requestoptions->ascending ? mysql_real_escape_string($valuecol) : mysql_real_escape_string($valuecol) . ' DESC';
|
| 182 | + }
|
| 183 | + }
|
| 184 | + }
|
| 185 | + return $sql_options;
|
| 186 | + }
|
| 187 | +
|
| 188 | + public static function getSQLOptionsAsString($requestoptions, $valuecol = NULL) {
|
| 189 | + $options = SNMDBHelper::getSQLOptions($requestoptions,$valuecol);
|
| 190 | + $limit = array_key_exists('LIMIT', $options) && is_numeric($options['LIMIT'])? 'LIMIT '.$options['LIMIT'] : '';
|
| 191 | + $offset = array_key_exists('OFFSET', $options) && is_numeric($options['OFFSET']) ? 'OFFSET '.$options['OFFSET'] : '';
|
| 192 | + $orderby = array_key_exists('ORDER BY', $options) ? 'ORDER BY '.$options['ORDER BY'] : '';
|
| 193 | + return $orderby.' '.$limit.' '.$offset;
|
| 194 | + }
|
| 195 | +
|
| 196 | + /**
|
| 197 | + * Transform input parameters into a suitable string of additional SQL conditions.
|
| 198 | + * The parameter $valuecol defines the string name of the column to which
|
| 199 | + * value restrictions etc. are to be applied.
|
| 200 | + * @param $requestoptions object with options
|
| 201 | + * @param $valuecol name of SQL column to which conditions apply
|
| 202 | + * @param $labelcol name of SQL column to which string conditions apply, if any
|
| 203 | + */
|
| 204 | + public static function getSQLConditions($requestoptions, $valuecol, $labelcol = NULL) {
|
| 205 | + $sql_conds = '';
|
| 206 | + if ($requestoptions !== NULL) {
|
| 207 | + $db =& wfGetDB( DB_SLAVE ); // TODO: use slave?
|
| 208 | + if ($requestoptions->boundary !== NULL) { // apply value boundary
|
| 209 | + if ($requestoptions->ascending) {
|
| 210 | + if ($requestoptions->include_boundary) {
|
| 211 | + $op = ' >= ';
|
| 212 | + } else {
|
| 213 | + $op = ' > ';
|
| 214 | + }
|
| 215 | + } else {
|
| 216 | + if ($requestoptions->include_boundary) {
|
| 217 | + $op = ' <= ';
|
| 218 | + } else {
|
| 219 | + $op = ' < ';
|
| 220 | + }
|
| 221 | + }
|
| 222 | + $sql_conds .= ' AND ' . mysql_real_escape_string($valuecol) . $op . $db->addQuotes($requestoptions->boundary);
|
| 223 | + }
|
| 224 | + $operator = isset($requestoptions->disjunctiveStrings) && $requestoptions->disjunctiveStrings === true ? ' OR ' : ' AND ';
|
| 225 | + $neutral = isset($requestoptions->disjunctiveStrings) && $requestoptions->disjunctiveStrings === true ? ' FALSE ' : ' TRUE ';
|
| 226 | + if ($labelcol !== NULL) { // apply string conditions
|
| 227 | +
|
| 228 | + $sql_conds .= ' AND ( ';
|
| 229 | +
|
| 230 | + foreach ($requestoptions->getStringConditions() as $strcond) {
|
| 231 | + $string = str_replace(array('_', ' '), array('\_', '\_'), $strcond->string);
|
| 232 | + switch ($strcond->condition) {
|
| 233 | + case SMWStringCondition::STRCOND_PRE:
|
| 234 | + $string .= '%';
|
| 235 | + break;
|
| 236 | + case SMWStringCondition::STRCOND_POST:
|
| 237 | + $string = '%' . $string;
|
| 238 | + break;
|
| 239 | + case SMWStringCondition::STRCOND_MID:
|
| 240 | + $string = '%' . $string . '%';
|
| 241 | + break;
|
| 242 | + }
|
| 243 | + if ($requestoptions->isCaseSensitive) {
|
| 244 | + $sql_conds .= mysql_real_escape_string($labelcol) . ' LIKE ' . $db->addQuotes($string). $operator;
|
| 245 | + } else {
|
| 246 | + $sql_conds .= ' UPPER(' . mysql_real_escape_string($labelcol) . ') LIKE UPPER(' . $db->addQuotes($string).') '.$operator;
|
| 247 | + }
|
| 248 | + }
|
| 249 | + $sql_conds .= ' '.$neutral.' ) ';
|
| 250 | + }
|
| 251 | + }
|
| 252 | + return $sql_conds;
|
| 253 | + }
|
| 254 | +
|
| 255 | + /**
|
| 256 | + * Returns sql conditions of $requestoptions in an Array.
|
| 257 | + * Warning! Does not support SMWAdvRequestOptions
|
| 258 | + *
|
| 259 | + * @param SMWRequestOptions $requestoptions
|
| 260 | + * @param string $valuecol
|
| 261 | + * @param string $labelcol
|
| 262 | + * @return array
|
| 263 | + */
|
| 264 | + public static function getSQLConditionsAsArray($requestoptions, $valuecol, $labelcol = NULL) {
|
| 265 | + $sql_conds = array();
|
| 266 | + if ($requestoptions !== NULL) {
|
| 267 | + $db =& wfGetDB( DB_SLAVE );
|
| 268 | + if ($requestoptions->boundary !== NULL) { // apply value boundary
|
| 269 | + if ($requestoptions->ascending) {
|
| 270 | + if ($requestoptions->include_boundary) {
|
| 271 | + $op = ' >= ';
|
| 272 | + } else {
|
| 273 | + $op = ' > ';
|
| 274 | + }
|
| 275 | + } else {
|
| 276 | + if ($requestoptions->include_boundary) {
|
| 277 | + $op = ' <= ';
|
| 278 | + } else {
|
| 279 | + $op = ' < ';
|
| 280 | + }
|
| 281 | + }
|
| 282 | + $sql_conds[] = mysql_real_escape_string($valuecol) . $op . $db->addQuotes($requestoptions->boundary);
|
| 283 | + }
|
| 284 | + if ($labelcol !== NULL) { // apply string conditions
|
| 285 | + foreach ($requestoptions->getStringConditions() as $strcond) {
|
| 286 | + $string = str_replace(array('_', ' '), array('\_', '\_'), $strcond->string);
|
| 287 | + switch ($strcond->condition) {
|
| 288 | + case SMWStringCondition::STRCOND_PRE:
|
| 289 | + $string .= '%';
|
| 290 | + break;
|
| 291 | + case SMWStringCondition::STRCOND_POST:
|
| 292 | + $string = '%' . $string;
|
| 293 | + break;
|
| 294 | + case SMWStringCondition::STRCOND_MID:
|
| 295 | + $string = '%' . $string . '%';
|
| 296 | + break;
|
| 297 | + }
|
| 298 | + $sql_conds[] = 'UPPER('.mysql_real_escape_string($labelcol) . ') LIKE UPPER(' . $db->addQuotes($string).')';
|
| 299 | + }
|
| 300 | + }
|
| 301 | + }
|
| 302 | + return $sql_conds;
|
| 303 | + }
|
| 304 | +}
|
| 305 | +?>
|
Index: trunk/extensions/SemanticNotifyMe/includes/jobs/SMW_NMRefreshJob.php |
— | — | @@ -0,0 +1,41 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @author dch
|
| 5 | + */ |
| 6 | + |
| 7 | +if ( !defined( 'MEDIAWIKI' ) ) {
|
| 8 | + die( "This file is part of the Semantic NotifyMe Extension. It is not a valid entry point.\n" );
|
| 9 | +}
|
| 10 | +global $IP;
|
| 11 | +require_once( "$IP/includes/JobQueue.php" );
|
| 12 | +
|
| 13 | +class SMWNMRefreshJob extends Job { |
| 14 | + |
| 15 | + function __construct(Title $title) { |
| 16 | + parent::__construct( 'SMWNMRefreshJob', $title); |
| 17 | + } |
| 18 | + |
| 19 | + /** |
| 20 | + * Run job |
| 21 | + * @return boolean success |
| 22 | + */ |
| 23 | + function run() { |
| 24 | + wfProfileIn('SMWNMRefreshJob::run (SMW)');
|
| 25 | + SMWNotifyProcessor::refreshNotifyMe();
|
| 26 | + wfProfileOut('SMWNMRefreshJob::run (SMW)'); |
| 27 | + return true; |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * This actually files the job. This is prevented if the configuration of SMW |
| 32 | + * disables jobs. |
| 33 | + * NOTE: Any method that inserts jobs with Job::batchInsert or otherwise must |
| 34 | + * implement this check individually. The below is not called in these cases. |
| 35 | + */ |
| 36 | + function insert() { |
| 37 | + global $smwgEnableUpdateJobs; |
| 38 | + if ($smwgEnableUpdateJobs) { |
| 39 | + parent::insert(); |
| 40 | + } |
| 41 | + } |
| 42 | +} |
Index: trunk/extensions/SemanticNotifyMe/includes/jobs/SMW_NMSendMailJob.php |
— | — | @@ -0,0 +1,52 @@ |
| 2 | +<?php
|
| 3 | +/*
|
| 4 | + * SMW_NMSendMailJob.php
|
| 5 | + *
|
| 6 | + * This job is triggered whenever a notify-me page was saved or removed.
|
| 7 | + *
|
| 8 | + * @author dch
|
| 9 | + *
|
| 10 | + */
|
| 11 | +if ( !defined( 'MEDIAWIKI' ) ) {
|
| 12 | + die( "This file is part of the Semantic NotifyMe Extension. It is not a valid entry point.\n" );
|
| 13 | +}
|
| 14 | +global $IP;
|
| 15 | +require_once( "$IP/includes/JobQueue.php" );
|
| 16 | +
|
| 17 | +
|
| 18 | +class SMW_NMSendMailJob extends Job {
|
| 19 | +
|
| 20 | + /**
|
| 21 | + * Creates a NMSendMailJob
|
| 22 | + *
|
| 23 | + * @param Title $title
|
| 24 | + */
|
| 25 | + function __construct($title, $params) {
|
| 26 | + wfDebug(__METHOD__." ".get_class($this)." \r\n");
|
| 27 | + wfProfileIn( __METHOD__ );
|
| 28 | + parent::__construct( get_class($this), Title::newMainPage(), $params);
|
| 29 | +
|
| 30 | + wfProfileOut( __METHOD__ );
|
| 31 | + }
|
| 32 | +
|
| 33 | + /**
|
| 34 | + * Run a SMW_NMSendMailJob job
|
| 35 | + * @return boolean success
|
| 36 | + */
|
| 37 | + function run() {
|
| 38 | + wfDebug(__METHOD__);
|
| 39 | + wfProfileIn( __METHOD__ );
|
| 40 | +
|
| 41 | + UserMailer::send( //userMailer(
|
| 42 | + $this->params['to'],
|
| 43 | + $this->params['from'],
|
| 44 | + $this->params['subj'],
|
| 45 | + $this->params['body'],
|
| 46 | + $this->params['replyto']
|
| 47 | + );
|
| 48 | +
|
| 49 | + wfProfileOut( __METHOD__ );
|
| 50 | + return true;
|
| 51 | + }
|
| 52 | +}
|
| 53 | +?>
|
Index: trunk/extensions/SemanticNotifyMe/languages/SMW_NMLanguage.php |
— | — | @@ -0,0 +1,138 @@ |
| 2 | +<?php
|
| 3 | +/**
|
| 4 | + * @author dch
|
| 5 | + */
|
| 6 | +
|
| 7 | +/**
|
| 8 | + * Base class for all language classes.
|
| 9 | + */
|
| 10 | +abstract class SMW_NMLanguage {
|
| 11 | +
|
| 12 | + // the message arrays ...
|
| 13 | + protected $smwContentMessages;
|
| 14 | + protected $smwUserMessages;
|
| 15 | + protected $smwDatatypeLabels;
|
| 16 | + protected $smwSpecialProperties;
|
| 17 | + protected $smwSpecialSchemaProperties;
|
| 18 | + protected $smwNMDatatypes;
|
| 19 | + protected $smwNMNamespaces;
|
| 20 | + protected $smwNMNamespaceAliases;
|
| 21 | +
|
| 22 | + /**
|
| 23 | + * Function that returns an array of namespace identifiers. This function
|
| 24 | + * is obsolete
|
| 25 | + */
|
| 26 | + abstract function getNamespaceArray();
|
| 27 | +
|
| 28 | +
|
| 29 | + /**
|
| 30 | + * Find the internal message id of some localised message string
|
| 31 | + * for a datatype. If no type of the given name exists (maybe a
|
| 32 | + * custom of compound type) then FALSE is returned.
|
| 33 | + */
|
| 34 | + function findDatatypeMsgID($label) {
|
| 35 | + return array_search($label, $this->smwDatatypeLabels);
|
| 36 | + }
|
| 37 | +
|
| 38 | + /**
|
| 39 | + * Registers all special properties of this extension in Semantic Media Wiki.
|
| 40 | + *
|
| 41 | + * The language files of the NM extension contain a mapping from special
|
| 42 | + * property constants to their string representation. These mappings are
|
| 43 | + * added to the mapping defined by Semantic Media Wiki.
|
| 44 | + */
|
| 45 | + function registerSpecialProperties() {
|
| 46 | + global $smwgContLang;
|
| 47 | + foreach ($this->smwSpecialProperties as $key => $prop) {
|
| 48 | + list($typeid, $label) = $prop;
|
| 49 | + SMWPropertyValue::registerProperty($key, $typeid, $label, true);
|
| 50 | +
|
| 51 | + }
|
| 52 | + }
|
| 53 | +
|
| 54 | + /**
|
| 55 | + * Returns the label of the special property with the ID $propID.
|
| 56 | + * @param int propID
|
| 57 | + * ID of the special property
|
| 58 | + * @return String Label of the special property
|
| 59 | + */
|
| 60 | + function getSpecialPropertyLabel($propID) {
|
| 61 | + return $this->smwSpecialProperties[$propID];
|
| 62 | + }
|
| 63 | +
|
| 64 | + /**
|
| 65 | + * Returns all labels of the special properties.
|
| 66 | + * @return array<String> Labels of the special properties
|
| 67 | + */
|
| 68 | + function getSpecialPropertyLabels() {
|
| 69 | + return $this->smwSpecialProperties;
|
| 70 | + }
|
| 71 | +
|
| 72 | + function getSpecialSchemaPropertyArray() {
|
| 73 | + return $this->smwSpecialSchemaProperties;
|
| 74 | + }
|
| 75 | +
|
| 76 | + function getSpecialCategoryArray() {
|
| 77 | + return $this->smwSpecialCategories;
|
| 78 | + }
|
| 79 | +
|
| 80 | + function getNMDatatype($datatypeID) {
|
| 81 | + return $this->smwNMDatatypes[$datatypeID];
|
| 82 | + }
|
| 83 | +
|
| 84 | + /**
|
| 85 | + * Function that returns all content messages (those that are stored
|
| 86 | + * in some article, and can thus not be translated to individual users).
|
| 87 | + */
|
| 88 | + function getContentMsgArray() {
|
| 89 | + return $this->smwContentMessages;
|
| 90 | + }
|
| 91 | +
|
| 92 | + /**
|
| 93 | + * Function that returns all user messages (those that are given only to
|
| 94 | + * the current user, and can thus be given in the individual user language).
|
| 95 | + */
|
| 96 | + function getUserMsgArray() {
|
| 97 | + return $this->smwUserMessages;
|
| 98 | + }
|
| 99 | +
|
| 100 | + /**
|
| 101 | + * Returns the name of the namespace with the ID <$namespaceID>.
|
| 102 | + *
|
| 103 | + * @param int $namespaceID
|
| 104 | + * ID of the namespace whose name is requested
|
| 105 | + * @return string
|
| 106 | + * Name of the namespace or <null>.
|
| 107 | + *
|
| 108 | + */
|
| 109 | + public function getNamespace($namespaceID) {
|
| 110 | + return $this->smwNMNamespaces[$namespaceID];
|
| 111 | + }
|
| 112 | +
|
| 113 | + /**
|
| 114 | + * Returns the array with all namespaces of the NM extension.
|
| 115 | + *
|
| 116 | + * @return string
|
| 117 | + * Array of additional namespaces.
|
| 118 | + *
|
| 119 | + */
|
| 120 | + public function getNamespaces() {
|
| 121 | + return $this->smwNMNamespaces;
|
| 122 | + }
|
| 123 | +
|
| 124 | + /**
|
| 125 | + * Returns the array with all namespace aliases of the NM extension.
|
| 126 | + *
|
| 127 | + * @return string
|
| 128 | + * Array of additional namespace aliases.
|
| 129 | + *
|
| 130 | + */
|
| 131 | + public function getNamespaceAliases() {
|
| 132 | + return $this->smwNMNamespaceAliases;
|
| 133 | + }
|
| 134 | +
|
| 135 | +
|
| 136 | +
|
| 137 | +}
|
| 138 | +
|
| 139 | +
|
Index: trunk/extensions/SemanticNotifyMe/languages/SMW_NMLanguageEn.php |
— | — | @@ -0,0 +1,59 @@ |
| 2 | +<?php
|
| 3 | +/**
|
| 4 | + * @author dch
|
| 5 | + */
|
| 6 | +
|
| 7 | +global $smwgNMIP;
|
| 8 | +include_once($smwgNMIP . '/languages/SMW_NMLanguage.php');
|
| 9 | +
|
| 10 | +class SMW_NMLanguageEn extends SMW_NMLanguage {
|
| 11 | +
|
| 12 | + protected $smwContentMessages = array(
|
| 13 | +
|
| 14 | + );
|
| 15 | +
|
| 16 | +
|
| 17 | + protected $smwUserMessages = array(
|
| 18 | + /*Messages for Notify Me*/
|
| 19 | + 'notifyme' => 'Notify Me',
|
| 20 | + 'smw_notifyme' => 'Notify Me',
|
| 21 | +
|
| 22 | + 'smw_qi_addNotify' => 'Notify me',
|
| 23 | + 'smw_qi_tt_addNotify' => 'Notify me when article-updates meet query condition',
|
| 24 | + 'smw_nm_tt_query' => 'Add #ask query to Notify Me',
|
| 25 | + 'smw_nm_tt_qtext' => 'Support query in {{#ask syntax',
|
| 26 | + 'smw_nm_tt_nmm' => 'Notify Me Manager enable you to control your notifications',
|
| 27 | + 'smw_nm_tt_clipboard' => 'Copies your RSS Feed URL to the clipboard so it can easily be inserted into any RSS reader',
|
| 28 | + );
|
| 29 | +
|
| 30 | +
|
| 31 | + protected $smwSpecialProperties = array(
|
| 32 | + );
|
| 33 | +
|
| 34 | +
|
| 35 | + var $smwSpecialSchemaProperties = array (
|
| 36 | + );
|
| 37 | +
|
| 38 | + var $smwSpecialCategories = array (
|
| 39 | + );
|
| 40 | +
|
| 41 | + var $smwNMDatatypes = array(
|
| 42 | + );
|
| 43 | +
|
| 44 | + protected $smwNMNamespaces = array(
|
| 45 | + );
|
| 46 | +
|
| 47 | + protected $smwNMNamespaceAliases = array(
|
| 48 | + );
|
| 49 | +
|
| 50 | + /**
|
| 51 | + * Function that returns the namespace identifiers. This is probably obsolete!
|
| 52 | + */
|
| 53 | + public function getNamespaceArray() {
|
| 54 | + return array();
|
| 55 | + }
|
| 56 | +
|
| 57 | +
|
| 58 | +}
|
| 59 | +
|
| 60 | +
|
Index: trunk/extensions/SemanticNotifyMe/scripts/prototype.js |
— | — | @@ -0,0 +1,4185 @@ |
| 2 | +/* Prototype JavaScript framework, version 1.6.0
|
| 3 | + * (c) 2005-2007 Sam Stephenson
|
| 4 | + *
|
| 5 | + * Prototype is freely distributable under the terms of an MIT-style license.
|
| 6 | + * For details, see the Prototype web site: http://www.prototypejs.org/
|
| 7 | + *
|
| 8 | + *--------------------------------------------------------------------------*/
|
| 9 | +
|
| 10 | +var Prototype = {
|
| 11 | + Version: '1.6.0',
|
| 12 | +
|
| 13 | + Browser: {
|
| 14 | + IE: !!(window.attachEvent && !window.opera),
|
| 15 | + Opera: !!window.opera,
|
| 16 | + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
|
| 17 | + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
|
| 18 | + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
|
| 19 | + },
|
| 20 | +
|
| 21 | + BrowserFeatures: {
|
| 22 | + XPath: !!document.evaluate,
|
| 23 | + ElementExtensions: !!window.HTMLElement,
|
| 24 | + SpecificElementExtensions:
|
| 25 | + document.createElement('div').__proto__ &&
|
| 26 | + document.createElement('div').__proto__ !==
|
| 27 | + document.createElement('form').__proto__
|
| 28 | + },
|
| 29 | +
|
| 30 | + ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
|
| 31 | + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
|
| 32 | +
|
| 33 | + emptyFunction: function() { },
|
| 34 | + K: function(x) { return x }
|
| 35 | +};
|
| 36 | +
|
| 37 | +if (Prototype.Browser.MobileSafari)
|
| 38 | + Prototype.BrowserFeatures.SpecificElementExtensions = false;
|
| 39 | +
|
| 40 | +if (Prototype.Browser.WebKit)
|
| 41 | + Prototype.BrowserFeatures.XPath = false;
|
| 42 | +
|
| 43 | +/* Based on Alex Arnell's inheritance implementation. */
|
| 44 | +var Class = {
|
| 45 | + create: function() {
|
| 46 | + var parent = null, properties = $A(arguments);
|
| 47 | + if (Object.isFunction(properties[0]))
|
| 48 | + parent = properties.shift();
|
| 49 | +
|
| 50 | + function klass() {
|
| 51 | + this.initialize.apply(this, arguments);
|
| 52 | + }
|
| 53 | +
|
| 54 | + Object.extend(klass, Class.Methods);
|
| 55 | + klass.superclass = parent;
|
| 56 | + klass.subclasses = [];
|
| 57 | +
|
| 58 | + if (parent) {
|
| 59 | + var subclass = function() { };
|
| 60 | + subclass.prototype = parent.prototype;
|
| 61 | + klass.prototype = new subclass;
|
| 62 | + parent.subclasses.push(klass);
|
| 63 | + }
|
| 64 | +
|
| 65 | + for (var i = 0; i < properties.length; i++)
|
| 66 | + klass.addMethods(properties[i]);
|
| 67 | +
|
| 68 | + if (!klass.prototype.initialize)
|
| 69 | + klass.prototype.initialize = Prototype.emptyFunction;
|
| 70 | +
|
| 71 | + klass.prototype.constructor = klass;
|
| 72 | +
|
| 73 | + return klass;
|
| 74 | + }
|
| 75 | +};
|
| 76 | +
|
| 77 | +Class.Methods = {
|
| 78 | + addMethods: function(source) {
|
| 79 | + var ancestor = this.superclass && this.superclass.prototype;
|
| 80 | + var properties = Object.keys(source);
|
| 81 | +
|
| 82 | + if (!Object.keys({ toString: true }).length)
|
| 83 | + properties.push("toString", "valueOf");
|
| 84 | +
|
| 85 | + for (var i = 0, length = properties.length; i < length; i++) {
|
| 86 | + var property = properties[i], value = source[property];
|
| 87 | + if (ancestor && Object.isFunction(value) &&
|
| 88 | + value.argumentNames().first() == "$super") {
|
| 89 | + var method = value, value = Object.extend((function(m) {
|
| 90 | + return function() { return ancestor[m].apply(this, arguments) };
|
| 91 | + })(property).wrap(method), {
|
| 92 | + valueOf: function() { return method },
|
| 93 | + toString: function() { return method.toString() }
|
| 94 | + });
|
| 95 | + }
|
| 96 | + this.prototype[property] = value;
|
| 97 | + }
|
| 98 | +
|
| 99 | + return this;
|
| 100 | + }
|
| 101 | +};
|
| 102 | +
|
| 103 | +var Abstract = { };
|
| 104 | +
|
| 105 | +Object.extend = function(destination, source) {
|
| 106 | + for (var property in source)
|
| 107 | + destination[property] = source[property];
|
| 108 | + return destination;
|
| 109 | +};
|
| 110 | +
|
| 111 | +Object.extend(Object, {
|
| 112 | + inspect: function(object) {
|
| 113 | + try {
|
| 114 | + if (object === undefined) return 'undefined';
|
| 115 | + if (object === null) return 'null';
|
| 116 | + return object.inspect ? object.inspect() : object.toString();
|
| 117 | + } catch (e) {
|
| 118 | + if (e instanceof RangeError) return '...';
|
| 119 | + throw e;
|
| 120 | + }
|
| 121 | + },
|
| 122 | +
|
| 123 | + toJSON: function(object) {
|
| 124 | + var type = typeof object;
|
| 125 | + switch (type) {
|
| 126 | + case 'undefined':
|
| 127 | + case 'function':
|
| 128 | + case 'unknown': return;
|
| 129 | + case 'boolean': return object.toString();
|
| 130 | + }
|
| 131 | +
|
| 132 | + if (object === null) return 'null';
|
| 133 | + if (object.toJSON) return object.toJSON();
|
| 134 | + if (Object.isElement(object)) return;
|
| 135 | +
|
| 136 | + var results = [];
|
| 137 | + for (var property in object) {
|
| 138 | + var value = Object.toJSON(object[property]);
|
| 139 | + if (value !== undefined)
|
| 140 | + results.push(property.toJSON() + ': ' + value);
|
| 141 | + }
|
| 142 | +
|
| 143 | + return '{' + results.join(', ') + '}';
|
| 144 | + },
|
| 145 | +
|
| 146 | + toQueryString: function(object) {
|
| 147 | + return $H(object).toQueryString();
|
| 148 | + },
|
| 149 | +
|
| 150 | + toHTML: function(object) {
|
| 151 | + return object && object.toHTML ? object.toHTML() : String.interpret(object);
|
| 152 | + },
|
| 153 | +
|
| 154 | + keys: function(object) {
|
| 155 | + var keys = [];
|
| 156 | + for (var property in object)
|
| 157 | + keys.push(property);
|
| 158 | + return keys;
|
| 159 | + },
|
| 160 | +
|
| 161 | + values: function(object) {
|
| 162 | + var values = [];
|
| 163 | + for (var property in object)
|
| 164 | + values.push(object[property]);
|
| 165 | + return values;
|
| 166 | + },
|
| 167 | +
|
| 168 | + clone: function(object) {
|
| 169 | + return Object.extend({ }, object);
|
| 170 | + },
|
| 171 | +
|
| 172 | + isElement: function(object) {
|
| 173 | + return object && object.nodeType == 1;
|
| 174 | + },
|
| 175 | +
|
| 176 | + isArray: function(object) {
|
| 177 | + return object && object.constructor === Array;
|
| 178 | + },
|
| 179 | +
|
| 180 | + isHash: function(object) {
|
| 181 | + return object instanceof Hash;
|
| 182 | + },
|
| 183 | +
|
| 184 | + isFunction: function(object) {
|
| 185 | + return typeof object == "function";
|
| 186 | + },
|
| 187 | +
|
| 188 | + isString: function(object) {
|
| 189 | + return typeof object == "string";
|
| 190 | + },
|
| 191 | +
|
| 192 | + isNumber: function(object) {
|
| 193 | + return typeof object == "number";
|
| 194 | + },
|
| 195 | +
|
| 196 | + isUndefined: function(object) {
|
| 197 | + return typeof object == "undefined";
|
| 198 | + }
|
| 199 | +});
|
| 200 | +
|
| 201 | +Object.extend(Function.prototype, {
|
| 202 | + argumentNames: function() {
|
| 203 | + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
|
| 204 | + return names.length == 1 && !names[0] ? [] : names;
|
| 205 | + },
|
| 206 | +
|
| 207 | + bind: function() {
|
| 208 | + if (arguments.length < 2 && arguments[0] === undefined) return this;
|
| 209 | + var __method = this, args = $A(arguments), object = args.shift();
|
| 210 | + return function() {
|
| 211 | + return __method.apply(object, args.concat($A(arguments)));
|
| 212 | + }
|
| 213 | + },
|
| 214 | +
|
| 215 | + bindAsEventListener: function() {
|
| 216 | + var __method = this, args = $A(arguments), object = args.shift();
|
| 217 | + return function(event) {
|
| 218 | + return __method.apply(object, [event || window.event].concat(args));
|
| 219 | + }
|
| 220 | + },
|
| 221 | +
|
| 222 | + curry: function() {
|
| 223 | + if (!arguments.length) return this;
|
| 224 | + var __method = this, args = $A(arguments);
|
| 225 | + return function() {
|
| 226 | + return __method.apply(this, args.concat($A(arguments)));
|
| 227 | + }
|
| 228 | + },
|
| 229 | +
|
| 230 | + delay: function() {
|
| 231 | + var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
|
| 232 | + return window.setTimeout(function() {
|
| 233 | + return __method.apply(__method, args);
|
| 234 | + }, timeout);
|
| 235 | + },
|
| 236 | +
|
| 237 | + wrap: function(wrapper) {
|
| 238 | + var __method = this;
|
| 239 | + return function() {
|
| 240 | + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
|
| 241 | + }
|
| 242 | + },
|
| 243 | +
|
| 244 | + methodize: function() {
|
| 245 | + if (this._methodized) return this._methodized;
|
| 246 | + var __method = this;
|
| 247 | + return this._methodized = function() {
|
| 248 | + return __method.apply(null, [this].concat($A(arguments)));
|
| 249 | + };
|
| 250 | + }
|
| 251 | +});
|
| 252 | +
|
| 253 | +Function.prototype.defer = Function.prototype.delay.curry(0.01);
|
| 254 | +
|
| 255 | +Date.prototype.toJSON = function() {
|
| 256 | + return '"' + this.getUTCFullYear() + '-' +
|
| 257 | + (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
|
| 258 | + this.getUTCDate().toPaddedString(2) + 'T' +
|
| 259 | + this.getUTCHours().toPaddedString(2) + ':' +
|
| 260 | + this.getUTCMinutes().toPaddedString(2) + ':' +
|
| 261 | + this.getUTCSeconds().toPaddedString(2) + 'Z"';
|
| 262 | +};
|
| 263 | +
|
| 264 | +var Try = {
|
| 265 | + these: function() {
|
| 266 | + var returnValue;
|
| 267 | +
|
| 268 | + for (var i = 0, length = arguments.length; i < length; i++) {
|
| 269 | + var lambda = arguments[i];
|
| 270 | + try {
|
| 271 | + returnValue = lambda();
|
| 272 | + break;
|
| 273 | + } catch (e) { }
|
| 274 | + }
|
| 275 | +
|
| 276 | + return returnValue;
|
| 277 | + }
|
| 278 | +};
|
| 279 | +
|
| 280 | +RegExp.prototype.match = RegExp.prototype.test;
|
| 281 | +
|
| 282 | +RegExp.escape = function(str) {
|
| 283 | + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
|
| 284 | +};
|
| 285 | +
|
| 286 | +/*--------------------------------------------------------------------------*/
|
| 287 | +
|
| 288 | +var PeriodicalExecuter = Class.create({
|
| 289 | + initialize: function(callback, frequency) {
|
| 290 | + this.callback = callback;
|
| 291 | + this.frequency = frequency;
|
| 292 | + this.currentlyExecuting = false;
|
| 293 | +
|
| 294 | + this.registerCallback();
|
| 295 | + },
|
| 296 | +
|
| 297 | + registerCallback: function() {
|
| 298 | + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
|
| 299 | + },
|
| 300 | +
|
| 301 | + execute: function() {
|
| 302 | + this.callback(this);
|
| 303 | + },
|
| 304 | +
|
| 305 | + stop: function() {
|
| 306 | + if (!this.timer) return;
|
| 307 | + clearInterval(this.timer);
|
| 308 | + this.timer = null;
|
| 309 | + },
|
| 310 | +
|
| 311 | + onTimerEvent: function() {
|
| 312 | + if (!this.currentlyExecuting) {
|
| 313 | + try {
|
| 314 | + this.currentlyExecuting = true;
|
| 315 | + this.execute();
|
| 316 | + } finally {
|
| 317 | + this.currentlyExecuting = false;
|
| 318 | + }
|
| 319 | + }
|
| 320 | + }
|
| 321 | +});
|
| 322 | +Object.extend(String, {
|
| 323 | + interpret: function(value) {
|
| 324 | + return value == null ? '' : String(value);
|
| 325 | + },
|
| 326 | + specialChar: {
|
| 327 | + '\b': '\\b',
|
| 328 | + '\t': '\\t',
|
| 329 | + '\n': '\\n',
|
| 330 | + '\f': '\\f',
|
| 331 | + '\r': '\\r',
|
| 332 | + '\\': '\\\\'
|
| 333 | + }
|
| 334 | +});
|
| 335 | +
|
| 336 | +Object.extend(String.prototype, {
|
| 337 | + gsub: function(pattern, replacement) {
|
| 338 | + var result = '', source = this, match;
|
| 339 | + replacement = arguments.callee.prepareReplacement(replacement);
|
| 340 | +
|
| 341 | + while (source.length > 0) {
|
| 342 | + if (match = source.match(pattern)) {
|
| 343 | + result += source.slice(0, match.index);
|
| 344 | + result += String.interpret(replacement(match));
|
| 345 | + source = source.slice(match.index + match[0].length);
|
| 346 | + } else {
|
| 347 | + result += source, source = '';
|
| 348 | + }
|
| 349 | + }
|
| 350 | + return result;
|
| 351 | + },
|
| 352 | +
|
| 353 | + sub: function(pattern, replacement, count) {
|
| 354 | + replacement = this.gsub.prepareReplacement(replacement);
|
| 355 | + count = count === undefined ? 1 : count;
|
| 356 | +
|
| 357 | + return this.gsub(pattern, function(match) {
|
| 358 | + if (--count < 0) return match[0];
|
| 359 | + return replacement(match);
|
| 360 | + });
|
| 361 | + },
|
| 362 | +
|
| 363 | + scan: function(pattern, iterator) {
|
| 364 | + this.gsub(pattern, iterator);
|
| 365 | + return String(this);
|
| 366 | + },
|
| 367 | +
|
| 368 | + truncate: function(length, truncation) {
|
| 369 | + length = length || 30;
|
| 370 | + truncation = truncation === undefined ? '...' : truncation;
|
| 371 | + return this.length > length ?
|
| 372 | + this.slice(0, length - truncation.length) + truncation : String(this);
|
| 373 | + },
|
| 374 | +
|
| 375 | + strip: function() {
|
| 376 | + return this.replace(/^\s+/, '').replace(/\s+$/, '');
|
| 377 | + },
|
| 378 | +
|
| 379 | + stripTags: function() {
|
| 380 | + return this.replace(/<\/?[^>]+>/gi, '');
|
| 381 | + },
|
| 382 | +
|
| 383 | + stripScripts: function() {
|
| 384 | + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
|
| 385 | + },
|
| 386 | +
|
| 387 | + extractScripts: function() {
|
| 388 | + var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
|
| 389 | + var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
|
| 390 | + return (this.match(matchAll) || []).map(function(scriptTag) {
|
| 391 | + return (scriptTag.match(matchOne) || ['', ''])[1];
|
| 392 | + });
|
| 393 | + },
|
| 394 | +
|
| 395 | + evalScripts: function() {
|
| 396 | + return this.extractScripts().map(function(script) { return eval(script) });
|
| 397 | + },
|
| 398 | +
|
| 399 | + escapeHTML: function() {
|
| 400 | + var self = arguments.callee;
|
| 401 | + self.text.data = this;
|
| 402 | + return self.div.innerHTML;
|
| 403 | + },
|
| 404 | +
|
| 405 | + unescapeHTML: function() {
|
| 406 | + var div = new Element('div');
|
| 407 | + div.innerHTML = this.stripTags();
|
| 408 | + return div.childNodes[0] ? (div.childNodes.length > 1 ?
|
| 409 | + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
|
| 410 | + div.childNodes[0].nodeValue) : '';
|
| 411 | + },
|
| 412 | +
|
| 413 | + toQueryParams: function(separator) {
|
| 414 | + var match = this.strip().match(/([^?#]*)(#.*)?$/);
|
| 415 | + if (!match) return { };
|
| 416 | +
|
| 417 | + return match[1].split(separator || '&').inject({ }, function(hash, pair) {
|
| 418 | + if ((pair = pair.split('='))[0]) {
|
| 419 | + var key = decodeURIComponent(pair.shift());
|
| 420 | + var value = pair.length > 1 ? pair.join('=') : pair[0];
|
| 421 | + if (value != undefined) value = decodeURIComponent(value);
|
| 422 | +
|
| 423 | + if (key in hash) {
|
| 424 | + if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
|
| 425 | + hash[key].push(value);
|
| 426 | + }
|
| 427 | + else hash[key] = value;
|
| 428 | + }
|
| 429 | + return hash;
|
| 430 | + });
|
| 431 | + },
|
| 432 | +
|
| 433 | + toArray: function() {
|
| 434 | + return this.split('');
|
| 435 | + },
|
| 436 | +
|
| 437 | + succ: function() {
|
| 438 | + return this.slice(0, this.length - 1) +
|
| 439 | + String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
|
| 440 | + },
|
| 441 | +
|
| 442 | + times: function(count) {
|
| 443 | + return count < 1 ? '' : new Array(count + 1).join(this);
|
| 444 | + },
|
| 445 | +
|
| 446 | + camelize: function() {
|
| 447 | + var parts = this.split('-'), len = parts.length;
|
| 448 | + if (len == 1) return parts[0];
|
| 449 | +
|
| 450 | + var camelized = this.charAt(0) == '-'
|
| 451 | + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
|
| 452 | + : parts[0];
|
| 453 | +
|
| 454 | + for (var i = 1; i < len; i++)
|
| 455 | + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
|
| 456 | +
|
| 457 | + return camelized;
|
| 458 | + },
|
| 459 | +
|
| 460 | + capitalize: function() {
|
| 461 | + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
|
| 462 | + },
|
| 463 | +
|
| 464 | + underscore: function() {
|
| 465 | + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
|
| 466 | + },
|
| 467 | +
|
| 468 | + dasherize: function() {
|
| 469 | + return this.gsub(/_/,'-');
|
| 470 | + },
|
| 471 | +
|
| 472 | + inspect: function(useDoubleQuotes) {
|
| 473 | + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
|
| 474 | + var character = String.specialChar[match[0]];
|
| 475 | + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
|
| 476 | + });
|
| 477 | + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
|
| 478 | + return "'" + escapedString.replace(/'/g, '\\\'') + "'";
|
| 479 | + },
|
| 480 | +
|
| 481 | + toJSON: function() {
|
| 482 | + return this.inspect(true);
|
| 483 | + },
|
| 484 | +
|
| 485 | + unfilterJSON: function(filter) {
|
| 486 | + return this.sub(filter || Prototype.JSONFilter, '#{1}');
|
| 487 | + },
|
| 488 | +
|
| 489 | + isJSON: function() {
|
| 490 | + var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
|
| 491 | + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
|
| 492 | + },
|
| 493 | +
|
| 494 | + evalJSON: function(sanitize) {
|
| 495 | + var json = this.unfilterJSON();
|
| 496 | + try {
|
| 497 | + if (!sanitize || json.isJSON()) return eval('(' + json + ')');
|
| 498 | + } catch (e) { }
|
| 499 | + throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
|
| 500 | + },
|
| 501 | +
|
| 502 | + include: function(pattern) {
|
| 503 | + return this.indexOf(pattern) > -1;
|
| 504 | + },
|
| 505 | +
|
| 506 | + startsWith: function(pattern) {
|
| 507 | + return this.indexOf(pattern) === 0;
|
| 508 | + },
|
| 509 | +
|
| 510 | + endsWith: function(pattern) {
|
| 511 | + var d = this.length - pattern.length;
|
| 512 | + return d >= 0 && this.lastIndexOf(pattern) === d;
|
| 513 | + },
|
| 514 | +
|
| 515 | + empty: function() {
|
| 516 | + return this == '';
|
| 517 | + },
|
| 518 | +
|
| 519 | + blank: function() {
|
| 520 | + return /^\s*$/.test(this);
|
| 521 | + },
|
| 522 | +
|
| 523 | + interpolate: function(object, pattern) {
|
| 524 | + return new Template(this, pattern).evaluate(object);
|
| 525 | + }
|
| 526 | +});
|
| 527 | +
|
| 528 | +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
|
| 529 | + escapeHTML: function() {
|
| 530 | + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
| 531 | + },
|
| 532 | + unescapeHTML: function() {
|
| 533 | + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
| 534 | + }
|
| 535 | +});
|
| 536 | +
|
| 537 | +String.prototype.gsub.prepareReplacement = function(replacement) {
|
| 538 | + if (Object.isFunction(replacement)) return replacement;
|
| 539 | + var template = new Template(replacement);
|
| 540 | + return function(match) { return template.evaluate(match) };
|
| 541 | +};
|
| 542 | +
|
| 543 | +String.prototype.parseQuery = String.prototype.toQueryParams;
|
| 544 | +
|
| 545 | +Object.extend(String.prototype.escapeHTML, {
|
| 546 | + div: document.createElement('div'),
|
| 547 | + text: document.createTextNode('')
|
| 548 | +});
|
| 549 | +
|
| 550 | +with (String.prototype.escapeHTML) div.appendChild(text);
|
| 551 | +
|
| 552 | +var Template = Class.create({
|
| 553 | + initialize: function(template, pattern) {
|
| 554 | + this.template = template.toString();
|
| 555 | + this.pattern = pattern || Template.Pattern;
|
| 556 | + },
|
| 557 | +
|
| 558 | + evaluate: function(object) {
|
| 559 | + if (Object.isFunction(object.toTemplateReplacements))
|
| 560 | + object = object.toTemplateReplacements();
|
| 561 | +
|
| 562 | + return this.template.gsub(this.pattern, function(match) {
|
| 563 | + if (object == null) return '';
|
| 564 | +
|
| 565 | + var before = match[1] || '';
|
| 566 | + if (before == '\\') return match[2];
|
| 567 | +
|
| 568 | + var ctx = object, expr = match[3];
|
| 569 | + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
|
| 570 | + if (match == null) return before;
|
| 571 | +
|
| 572 | + while (match != null) {
|
| 573 | + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
|
| 574 | + ctx = ctx[comp];
|
| 575 | + if (null == ctx || '' == match[3]) break;
|
| 576 | + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
|
| 577 | + match = pattern.exec(expr);
|
| 578 | + }
|
| 579 | +
|
| 580 | + return before + String.interpret(ctx);
|
| 581 | + }.bind(this));
|
| 582 | + }
|
| 583 | +});
|
| 584 | +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
|
| 585 | +
|
| 586 | +var $break = { };
|
| 587 | +
|
| 588 | +var Enumerable = {
|
| 589 | + each: function(iterator, context) {
|
| 590 | + var index = 0;
|
| 591 | + iterator = iterator.bind(context);
|
| 592 | + try {
|
| 593 | + this._each(function(value) {
|
| 594 | + iterator(value, index++);
|
| 595 | + });
|
| 596 | + } catch (e) {
|
| 597 | + if (e != $break) throw e;
|
| 598 | + }
|
| 599 | + return this;
|
| 600 | + },
|
| 601 | +
|
| 602 | + eachSlice: function(number, iterator, context) {
|
| 603 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 604 | + var index = -number, slices = [], array = this.toArray();
|
| 605 | + while ((index += number) < array.length)
|
| 606 | + slices.push(array.slice(index, index+number));
|
| 607 | + return slices.collect(iterator, context);
|
| 608 | + },
|
| 609 | +
|
| 610 | + all: function(iterator, context) {
|
| 611 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 612 | + var result = true;
|
| 613 | + this.each(function(value, index) {
|
| 614 | + result = result && !!iterator(value, index);
|
| 615 | + if (!result) throw $break;
|
| 616 | + });
|
| 617 | + return result;
|
| 618 | + },
|
| 619 | +
|
| 620 | + any: function(iterator, context) {
|
| 621 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 622 | + var result = false;
|
| 623 | + this.each(function(value, index) {
|
| 624 | + if (result = !!iterator(value, index))
|
| 625 | + throw $break;
|
| 626 | + });
|
| 627 | + return result;
|
| 628 | + },
|
| 629 | +
|
| 630 | + collect: function(iterator, context) {
|
| 631 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 632 | + var results = [];
|
| 633 | + this.each(function(value, index) {
|
| 634 | + results.push(iterator(value, index));
|
| 635 | + });
|
| 636 | + return results;
|
| 637 | + },
|
| 638 | +
|
| 639 | + detect: function(iterator, context) {
|
| 640 | + iterator = iterator.bind(context);
|
| 641 | + var result;
|
| 642 | + this.each(function(value, index) {
|
| 643 | + if (iterator(value, index)) {
|
| 644 | + result = value;
|
| 645 | + throw $break;
|
| 646 | + }
|
| 647 | + });
|
| 648 | + return result;
|
| 649 | + },
|
| 650 | +
|
| 651 | + findAll: function(iterator, context) {
|
| 652 | + iterator = iterator.bind(context);
|
| 653 | + var results = [];
|
| 654 | + this.each(function(value, index) {
|
| 655 | + if (iterator(value, index))
|
| 656 | + results.push(value);
|
| 657 | + });
|
| 658 | + return results;
|
| 659 | + },
|
| 660 | +
|
| 661 | + grep: function(filter, iterator, context) {
|
| 662 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 663 | + var results = [];
|
| 664 | +
|
| 665 | + if (Object.isString(filter))
|
| 666 | + filter = new RegExp(filter);
|
| 667 | +
|
| 668 | + this.each(function(value, index) {
|
| 669 | + if (filter.match(value))
|
| 670 | + results.push(iterator(value, index));
|
| 671 | + });
|
| 672 | + return results;
|
| 673 | + },
|
| 674 | +
|
| 675 | + include: function(object) {
|
| 676 | + if (Object.isFunction(this.indexOf))
|
| 677 | + if (this.indexOf(object) != -1) return true;
|
| 678 | +
|
| 679 | + var found = false;
|
| 680 | + this.each(function(value) {
|
| 681 | + if (value == object) {
|
| 682 | + found = true;
|
| 683 | + throw $break;
|
| 684 | + }
|
| 685 | + });
|
| 686 | + return found;
|
| 687 | + },
|
| 688 | +
|
| 689 | + inGroupsOf: function(number, fillWith) {
|
| 690 | + fillWith = fillWith === undefined ? null : fillWith;
|
| 691 | + return this.eachSlice(number, function(slice) {
|
| 692 | + while(slice.length < number) slice.push(fillWith);
|
| 693 | + return slice;
|
| 694 | + });
|
| 695 | + },
|
| 696 | +
|
| 697 | + inject: function(memo, iterator, context) {
|
| 698 | + iterator = iterator.bind(context);
|
| 699 | + this.each(function(value, index) {
|
| 700 | + memo = iterator(memo, value, index);
|
| 701 | + });
|
| 702 | + return memo;
|
| 703 | + },
|
| 704 | +
|
| 705 | + invoke: function(method) {
|
| 706 | + var args = $A(arguments).slice(1);
|
| 707 | + return this.map(function(value) {
|
| 708 | + return value[method].apply(value, args);
|
| 709 | + });
|
| 710 | + },
|
| 711 | +
|
| 712 | + max: function(iterator, context) {
|
| 713 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 714 | + var result;
|
| 715 | + this.each(function(value, index) {
|
| 716 | + value = iterator(value, index);
|
| 717 | + if (result == undefined || value >= result)
|
| 718 | + result = value;
|
| 719 | + });
|
| 720 | + return result;
|
| 721 | + },
|
| 722 | +
|
| 723 | + min: function(iterator, context) {
|
| 724 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 725 | + var result;
|
| 726 | + this.each(function(value, index) {
|
| 727 | + value = iterator(value, index);
|
| 728 | + if (result == undefined || value < result)
|
| 729 | + result = value;
|
| 730 | + });
|
| 731 | + return result;
|
| 732 | + },
|
| 733 | +
|
| 734 | + partition: function(iterator, context) {
|
| 735 | + iterator = iterator ? iterator.bind(context) : Prototype.K;
|
| 736 | + var trues = [], falses = [];
|
| 737 | + this.each(function(value, index) {
|
| 738 | + (iterator(value, index) ?
|
| 739 | + trues : falses).push(value);
|
| 740 | + });
|
| 741 | + return [trues, falses];
|
| 742 | + },
|
| 743 | +
|
| 744 | + pluck: function(property) {
|
| 745 | + var results = [];
|
| 746 | + this.each(function(value) {
|
| 747 | + results.push(value[property]);
|
| 748 | + });
|
| 749 | + return results;
|
| 750 | + },
|
| 751 | +
|
| 752 | + reject: function(iterator, context) {
|
| 753 | + iterator = iterator.bind(context);
|
| 754 | + var results = [];
|
| 755 | + this.each(function(value, index) {
|
| 756 | + if (!iterator(value, index))
|
| 757 | + results.push(value);
|
| 758 | + });
|
| 759 | + return results;
|
| 760 | + },
|
| 761 | +
|
| 762 | + sortBy: function(iterator, context) {
|
| 763 | + iterator = iterator.bind(context);
|
| 764 | + return this.map(function(value, index) {
|
| 765 | + return {value: value, criteria: iterator(value, index)};
|
| 766 | + }).sort(function(left, right) {
|
| 767 | + var a = left.criteria, b = right.criteria;
|
| 768 | + return a < b ? -1 : a > b ? 1 : 0;
|
| 769 | + }).pluck('value');
|
| 770 | + },
|
| 771 | +
|
| 772 | + toArray: function() {
|
| 773 | + return this.map();
|
| 774 | + },
|
| 775 | +
|
| 776 | + zip: function() {
|
| 777 | + var iterator = Prototype.K, args = $A(arguments);
|
| 778 | + if (Object.isFunction(args.last()))
|
| 779 | + iterator = args.pop();
|
| 780 | +
|
| 781 | + var collections = [this].concat(args).map($A);
|
| 782 | + return this.map(function(value, index) {
|
| 783 | + return iterator(collections.pluck(index));
|
| 784 | + });
|
| 785 | + },
|
| 786 | +
|
| 787 | + size: function() {
|
| 788 | + return this.toArray().length;
|
| 789 | + },
|
| 790 | +
|
| 791 | + inspect: function() {
|
| 792 | + return '#<Enumerable:' + this.toArray().inspect() + '>';
|
| 793 | + }
|
| 794 | +};
|
| 795 | +
|
| 796 | +Object.extend(Enumerable, {
|
| 797 | + map: Enumerable.collect,
|
| 798 | + find: Enumerable.detect,
|
| 799 | + select: Enumerable.findAll,
|
| 800 | + filter: Enumerable.findAll,
|
| 801 | + member: Enumerable.include,
|
| 802 | + entries: Enumerable.toArray,
|
| 803 | + every: Enumerable.all,
|
| 804 | + some: Enumerable.any
|
| 805 | +});
|
| 806 | +function $A(iterable) {
|
| 807 | + if (!iterable) return [];
|
| 808 | + if (iterable.toArray) return iterable.toArray();
|
| 809 | + var length = iterable.length, results = new Array(length);
|
| 810 | + while (length--) results[length] = iterable[length];
|
| 811 | + return results;
|
| 812 | +}
|
| 813 | +
|
| 814 | +if (Prototype.Browser.WebKit) {
|
| 815 | + function $A(iterable) {
|
| 816 | + if (!iterable) return [];
|
| 817 | + if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
|
| 818 | + iterable.toArray) return iterable.toArray();
|
| 819 | + var length = iterable.length, results = new Array(length);
|
| 820 | + while (length--) results[length] = iterable[length];
|
| 821 | + return results;
|
| 822 | + }
|
| 823 | +}
|
| 824 | +
|
| 825 | +Array.from = $A;
|
| 826 | +
|
| 827 | +Object.extend(Array.prototype, Enumerable);
|
| 828 | +
|
| 829 | +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
|
| 830 | +
|
| 831 | +Object.extend(Array.prototype, {
|
| 832 | + _each: function(iterator) {
|
| 833 | + for (var i = 0, length = this.length; i < length; i++)
|
| 834 | + iterator(this[i]);
|
| 835 | + },
|
| 836 | +
|
| 837 | + clear: function() {
|
| 838 | + this.length = 0;
|
| 839 | + return this;
|
| 840 | + },
|
| 841 | +
|
| 842 | + first: function() {
|
| 843 | + return this[0];
|
| 844 | + },
|
| 845 | +
|
| 846 | + last: function() {
|
| 847 | + return this[this.length - 1];
|
| 848 | + },
|
| 849 | +
|
| 850 | + compact: function() {
|
| 851 | + return this.select(function(value) {
|
| 852 | + return value != null;
|
| 853 | + });
|
| 854 | + },
|
| 855 | +
|
| 856 | + flatten: function() {
|
| 857 | + return this.inject([], function(array, value) {
|
| 858 | + return array.concat(Object.isArray(value) ?
|
| 859 | + value.flatten() : [value]);
|
| 860 | + });
|
| 861 | + },
|
| 862 | +
|
| 863 | + without: function() {
|
| 864 | + var values = $A(arguments);
|
| 865 | + return this.select(function(value) {
|
| 866 | + return !values.include(value);
|
| 867 | + });
|
| 868 | + },
|
| 869 | +
|
| 870 | + reverse: function(inline) {
|
| 871 | + return (inline !== false ? this : this.toArray())._reverse();
|
| 872 | + },
|
| 873 | +
|
| 874 | + reduce: function() {
|
| 875 | + return this.length > 1 ? this : this[0];
|
| 876 | + },
|
| 877 | +
|
| 878 | + uniq: function(sorted) {
|
| 879 | + return this.inject([], function(array, value, index) {
|
| 880 | + if (0 == index || (sorted ? array.last() != value : !array.include(value)))
|
| 881 | + array.push(value);
|
| 882 | + return array;
|
| 883 | + });
|
| 884 | + },
|
| 885 | +
|
| 886 | + intersect: function(array) {
|
| 887 | + return this.uniq().findAll(function(item) {
|
| 888 | + return array.detect(function(value) { return item === value });
|
| 889 | + });
|
| 890 | + },
|
| 891 | +
|
| 892 | + clone: function() {
|
| 893 | + return [].concat(this);
|
| 894 | + },
|
| 895 | +
|
| 896 | + size: function() {
|
| 897 | + return this.length;
|
| 898 | + },
|
| 899 | +
|
| 900 | + inspect: function() {
|
| 901 | + return '[' + this.map(Object.inspect).join(', ') + ']';
|
| 902 | + },
|
| 903 | +
|
| 904 | + toJSON: function() {
|
| 905 | + var results = [];
|
| 906 | + this.each(function(object) {
|
| 907 | + var value = Object.toJSON(object);
|
| 908 | + if (value !== undefined) results.push(value);
|
| 909 | + });
|
| 910 | + return '[' + results.join(', ') + ']';
|
| 911 | + }
|
| 912 | +});
|
| 913 | +
|
| 914 | +// use native browser JS 1.6 implementation if available
|
| 915 | +if (Object.isFunction(Array.prototype.forEach))
|
| 916 | + Array.prototype._each = Array.prototype.forEach;
|
| 917 | +
|
| 918 | +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
|
| 919 | + i || (i = 0);
|
| 920 | + var length = this.length;
|
| 921 | + if (i < 0) i = length + i;
|
| 922 | + for (; i < length; i++)
|
| 923 | + if (this[i] === item) return i;
|
| 924 | + return -1;
|
| 925 | +};
|
| 926 | +
|
| 927 | +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
|
| 928 | + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
|
| 929 | + var n = this.slice(0, i).reverse().indexOf(item);
|
| 930 | + return (n < 0) ? n : i - n - 1;
|
| 931 | +};
|
| 932 | +
|
| 933 | +Array.prototype.toArray = Array.prototype.clone;
|
| 934 | +
|
| 935 | +function $w(string) {
|
| 936 | + if (!Object.isString(string)) return [];
|
| 937 | + string = string.strip();
|
| 938 | + return string ? string.split(/\s+/) : [];
|
| 939 | +}
|
| 940 | +
|
| 941 | +if (Prototype.Browser.Opera){
|
| 942 | + Array.prototype.concat = function() {
|
| 943 | + var array = [];
|
| 944 | + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
|
| 945 | + for (var i = 0, length = arguments.length; i < length; i++) {
|
| 946 | + if (Object.isArray(arguments[i])) {
|
| 947 | + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
|
| 948 | + array.push(arguments[i][j]);
|
| 949 | + } else {
|
| 950 | + array.push(arguments[i]);
|
| 951 | + }
|
| 952 | + }
|
| 953 | + return array;
|
| 954 | + };
|
| 955 | +}
|
| 956 | +Object.extend(Number.prototype, {
|
| 957 | + toColorPart: function() {
|
| 958 | + return this.toPaddedString(2, 16);
|
| 959 | + },
|
| 960 | +
|
| 961 | + succ: function() {
|
| 962 | + return this + 1;
|
| 963 | + },
|
| 964 | +
|
| 965 | + times: function(iterator) {
|
| 966 | + $R(0, this, true).each(iterator);
|
| 967 | + return this;
|
| 968 | + },
|
| 969 | +
|
| 970 | + toPaddedString: function(length, radix) {
|
| 971 | + var string = this.toString(radix || 10);
|
| 972 | + return '0'.times(length - string.length) + string;
|
| 973 | + },
|
| 974 | +
|
| 975 | + toJSON: function() {
|
| 976 | + return isFinite(this) ? this.toString() : 'null';
|
| 977 | + }
|
| 978 | +});
|
| 979 | +
|
| 980 | +$w('abs round ceil floor').each(function(method){
|
| 981 | + Number.prototype[method] = Math[method].methodize();
|
| 982 | +});
|
| 983 | +function $H(object) {
|
| 984 | + return new Hash(object);
|
| 985 | +};
|
| 986 | +
|
| 987 | +var Hash = Class.create(Enumerable, (function() {
|
| 988 | + if (function() {
|
| 989 | + var i = 0, Test = function(value) { this.key = value };
|
| 990 | + Test.prototype.key = 'foo';
|
| 991 | + for (var property in new Test('bar')) i++;
|
| 992 | + return i > 1;
|
| 993 | + }()) {
|
| 994 | + function each(iterator) {
|
| 995 | + var cache = [];
|
| 996 | + for (var key in this._object) {
|
| 997 | + var value = this._object[key];
|
| 998 | + if (cache.include(key)) continue;
|
| 999 | + cache.push(key);
|
| 1000 | + var pair = [key, value];
|
| 1001 | + pair.key = key;
|
| 1002 | + pair.value = value;
|
| 1003 | + iterator(pair);
|
| 1004 | + }
|
| 1005 | + }
|
| 1006 | + } else {
|
| 1007 | + function each(iterator) {
|
| 1008 | + for (var key in this._object) {
|
| 1009 | + var value = this._object[key], pair = [key, value];
|
| 1010 | + pair.key = key;
|
| 1011 | + pair.value = value;
|
| 1012 | + iterator(pair);
|
| 1013 | + }
|
| 1014 | + }
|
| 1015 | + }
|
| 1016 | +
|
| 1017 | + function toQueryPair(key, value) {
|
| 1018 | + if (Object.isUndefined(value)) return key;
|
| 1019 | + return key + '=' + encodeURIComponent(String.interpret(value));
|
| 1020 | + }
|
| 1021 | +
|
| 1022 | + return {
|
| 1023 | + initialize: function(object) {
|
| 1024 | + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
|
| 1025 | + },
|
| 1026 | +
|
| 1027 | + _each: each,
|
| 1028 | +
|
| 1029 | + set: function(key, value) {
|
| 1030 | + return this._object[key] = value;
|
| 1031 | + },
|
| 1032 | +
|
| 1033 | + get: function(key) {
|
| 1034 | + return this._object[key];
|
| 1035 | + },
|
| 1036 | +
|
| 1037 | + unset: function(key) {
|
| 1038 | + var value = this._object[key];
|
| 1039 | + delete this._object[key];
|
| 1040 | + return value;
|
| 1041 | + },
|
| 1042 | +
|
| 1043 | + toObject: function() {
|
| 1044 | + return Object.clone(this._object);
|
| 1045 | + },
|
| 1046 | +
|
| 1047 | + keys: function() {
|
| 1048 | + return this.pluck('key');
|
| 1049 | + },
|
| 1050 | +
|
| 1051 | + values: function() {
|
| 1052 | + return this.pluck('value');
|
| 1053 | + },
|
| 1054 | +
|
| 1055 | + index: function(value) {
|
| 1056 | + var match = this.detect(function(pair) {
|
| 1057 | + return pair.value === value;
|
| 1058 | + });
|
| 1059 | + return match && match.key;
|
| 1060 | + },
|
| 1061 | +
|
| 1062 | + merge: function(object) {
|
| 1063 | + return this.clone().update(object);
|
| 1064 | + },
|
| 1065 | +
|
| 1066 | + update: function(object) {
|
| 1067 | + return new Hash(object).inject(this, function(result, pair) {
|
| 1068 | + result.set(pair.key, pair.value);
|
| 1069 | + return result;
|
| 1070 | + });
|
| 1071 | + },
|
| 1072 | +
|
| 1073 | + toQueryString: function() {
|
| 1074 | + return this.map(function(pair) {
|
| 1075 | + var key = encodeURIComponent(pair.key), values = pair.value;
|
| 1076 | +
|
| 1077 | + if (values && typeof values == 'object') {
|
| 1078 | + if (Object.isArray(values))
|
| 1079 | + return values.map(toQueryPair.curry(key)).join('&');
|
| 1080 | + }
|
| 1081 | + return toQueryPair(key, values);
|
| 1082 | + }).join('&');
|
| 1083 | + },
|
| 1084 | +
|
| 1085 | + inspect: function() {
|
| 1086 | + return '#<Hash:{' + this.map(function(pair) {
|
| 1087 | + return pair.map(Object.inspect).join(': ');
|
| 1088 | + }).join(', ') + '}>';
|
| 1089 | + },
|
| 1090 | +
|
| 1091 | + toJSON: function() {
|
| 1092 | + return Object.toJSON(this.toObject());
|
| 1093 | + },
|
| 1094 | +
|
| 1095 | + clone: function() {
|
| 1096 | + return new Hash(this);
|
| 1097 | + }
|
| 1098 | + }
|
| 1099 | +})());
|
| 1100 | +
|
| 1101 | +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
|
| 1102 | +Hash.from = $H;
|
| 1103 | +var ObjectRange = Class.create(Enumerable, {
|
| 1104 | + initialize: function(start, end, exclusive) {
|
| 1105 | + this.start = start;
|
| 1106 | + this.end = end;
|
| 1107 | + this.exclusive = exclusive;
|
| 1108 | + },
|
| 1109 | +
|
| 1110 | + _each: function(iterator) {
|
| 1111 | + var value = this.start;
|
| 1112 | + while (this.include(value)) {
|
| 1113 | + iterator(value);
|
| 1114 | + value = value.succ();
|
| 1115 | + }
|
| 1116 | + },
|
| 1117 | +
|
| 1118 | + include: function(value) {
|
| 1119 | + if (value < this.start)
|
| 1120 | + return false;
|
| 1121 | + if (this.exclusive)
|
| 1122 | + return value < this.end;
|
| 1123 | + return value <= this.end;
|
| 1124 | + }
|
| 1125 | +});
|
| 1126 | +
|
| 1127 | +var $R = function(start, end, exclusive) {
|
| 1128 | + return new ObjectRange(start, end, exclusive);
|
| 1129 | +};
|
| 1130 | +
|
| 1131 | +var Ajax = {
|
| 1132 | + getTransport: function() {
|
| 1133 | + return Try.these(
|
| 1134 | + function() {return new XMLHttpRequest()},
|
| 1135 | + function() {return new ActiveXObject('Msxml2.XMLHTTP')},
|
| 1136 | + function() {return new ActiveXObject('Microsoft.XMLHTTP')}
|
| 1137 | + ) || false;
|
| 1138 | + },
|
| 1139 | +
|
| 1140 | + activeRequestCount: 0
|
| 1141 | +};
|
| 1142 | +
|
| 1143 | +Ajax.Responders = {
|
| 1144 | + responders: [],
|
| 1145 | +
|
| 1146 | + _each: function(iterator) {
|
| 1147 | + this.responders._each(iterator);
|
| 1148 | + },
|
| 1149 | +
|
| 1150 | + register: function(responder) {
|
| 1151 | + if (!this.include(responder))
|
| 1152 | + this.responders.push(responder);
|
| 1153 | + },
|
| 1154 | +
|
| 1155 | + unregister: function(responder) {
|
| 1156 | + this.responders = this.responders.without(responder);
|
| 1157 | + },
|
| 1158 | +
|
| 1159 | + dispatch: function(callback, request, transport, json) {
|
| 1160 | + this.each(function(responder) {
|
| 1161 | + if (Object.isFunction(responder[callback])) {
|
| 1162 | + try {
|
| 1163 | + responder[callback].apply(responder, [request, transport, json]);
|
| 1164 | + } catch (e) { }
|
| 1165 | + }
|
| 1166 | + });
|
| 1167 | + }
|
| 1168 | +};
|
| 1169 | +
|
| 1170 | +Object.extend(Ajax.Responders, Enumerable);
|
| 1171 | +
|
| 1172 | +Ajax.Responders.register({
|
| 1173 | + onCreate: function() { Ajax.activeRequestCount++ },
|
| 1174 | + onComplete: function() { Ajax.activeRequestCount-- }
|
| 1175 | +});
|
| 1176 | +
|
| 1177 | +Ajax.Base = Class.create({
|
| 1178 | + initialize: function(options) {
|
| 1179 | + this.options = {
|
| 1180 | + method: 'post',
|
| 1181 | + asynchronous: true,
|
| 1182 | + contentType: 'application/x-www-form-urlencoded',
|
| 1183 | + encoding: 'UTF-8',
|
| 1184 | + parameters: '',
|
| 1185 | + evalJSON: true,
|
| 1186 | + evalJS: true
|
| 1187 | + };
|
| 1188 | + Object.extend(this.options, options || { });
|
| 1189 | +
|
| 1190 | + this.options.method = this.options.method.toLowerCase();
|
| 1191 | + if (Object.isString(this.options.parameters))
|
| 1192 | + this.options.parameters = this.options.parameters.toQueryParams();
|
| 1193 | + }
|
| 1194 | +});
|
| 1195 | +
|
| 1196 | +Ajax.Request = Class.create(Ajax.Base, {
|
| 1197 | + _complete: false,
|
| 1198 | +
|
| 1199 | + initialize: function($super, url, options) {
|
| 1200 | + $super(options);
|
| 1201 | + this.transport = Ajax.getTransport();
|
| 1202 | + this.request(url);
|
| 1203 | + },
|
| 1204 | +
|
| 1205 | + request: function(url) {
|
| 1206 | + this.url = url;
|
| 1207 | + this.method = this.options.method;
|
| 1208 | + var params = Object.clone(this.options.parameters);
|
| 1209 | +
|
| 1210 | + if (!['get', 'post'].include(this.method)) {
|
| 1211 | + // simulate other verbs over post
|
| 1212 | + params['_method'] = this.method;
|
| 1213 | + this.method = 'post';
|
| 1214 | + }
|
| 1215 | +
|
| 1216 | + this.parameters = params;
|
| 1217 | +
|
| 1218 | + if (params = Object.toQueryString(params)) {
|
| 1219 | + // when GET, append parameters to URL
|
| 1220 | + if (this.method == 'get')
|
| 1221 | + this.url += (this.url.include('?') ? '&' : '?') + params;
|
| 1222 | + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
|
| 1223 | + params += '&_=';
|
| 1224 | + }
|
| 1225 | +
|
| 1226 | + try {
|
| 1227 | + var response = new Ajax.Response(this);
|
| 1228 | + if (this.options.onCreate) this.options.onCreate(response);
|
| 1229 | + Ajax.Responders.dispatch('onCreate', this, response);
|
| 1230 | +
|
| 1231 | + this.transport.open(this.method.toUpperCase(), this.url,
|
| 1232 | + this.options.asynchronous);
|
| 1233 | +
|
| 1234 | + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
|
| 1235 | +
|
| 1236 | + this.transport.onreadystatechange = this.onStateChange.bind(this);
|
| 1237 | + this.setRequestHeaders();
|
| 1238 | +
|
| 1239 | + this.body = this.method == 'post' ? (this.options.postBody || params) : null;
|
| 1240 | + this.transport.send(this.body);
|
| 1241 | +
|
| 1242 | + /* Force Firefox to handle ready state 4 for synchronous requests */
|
| 1243 | + if (!this.options.asynchronous && this.transport.overrideMimeType)
|
| 1244 | + this.onStateChange();
|
| 1245 | +
|
| 1246 | + }
|
| 1247 | + catch (e) {
|
| 1248 | + this.dispatchException(e);
|
| 1249 | + }
|
| 1250 | + },
|
| 1251 | +
|
| 1252 | + onStateChange: function() {
|
| 1253 | + var readyState = this.transport.readyState;
|
| 1254 | + if (readyState > 1 && !((readyState == 4) && this._complete))
|
| 1255 | + this.respondToReadyState(this.transport.readyState);
|
| 1256 | + },
|
| 1257 | +
|
| 1258 | + setRequestHeaders: function() {
|
| 1259 | + var headers = {
|
| 1260 | + 'X-Requested-With': 'XMLHttpRequest',
|
| 1261 | + 'X-Prototype-Version': Prototype.Version,
|
| 1262 | + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
|
| 1263 | + };
|
| 1264 | +
|
| 1265 | + if (this.method == 'post') {
|
| 1266 | + headers['Content-type'] = this.options.contentType +
|
| 1267 | + (this.options.encoding ? '; charset=' + this.options.encoding : '');
|
| 1268 | +
|
| 1269 | + /* Force "Connection: close" for older Mozilla browsers to work
|
| 1270 | + * around a bug where XMLHttpRequest sends an incorrect
|
| 1271 | + * Content-length header. See Mozilla Bugzilla #246651.
|
| 1272 | + */
|
| 1273 | + if (this.transport.overrideMimeType &&
|
| 1274 | + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
|
| 1275 | + headers['Connection'] = 'close';
|
| 1276 | + }
|
| 1277 | +
|
| 1278 | + // user-defined headers
|
| 1279 | + if (typeof this.options.requestHeaders == 'object') {
|
| 1280 | + var extras = this.options.requestHeaders;
|
| 1281 | +
|
| 1282 | + if (Object.isFunction(extras.push))
|
| 1283 | + for (var i = 0, length = extras.length; i < length; i += 2)
|
| 1284 | + headers[extras[i]] = extras[i+1];
|
| 1285 | + else
|
| 1286 | + $H(extras).each(function(pair) { headers[pair.key] = pair.value });
|
| 1287 | + }
|
| 1288 | +
|
| 1289 | + for (var name in headers)
|
| 1290 | + this.transport.setRequestHeader(name, headers[name]);
|
| 1291 | + },
|
| 1292 | +
|
| 1293 | + success: function() {
|
| 1294 | + var status = this.getStatus();
|
| 1295 | + return !status || (status >= 200 && status < 300);
|
| 1296 | + },
|
| 1297 | +
|
| 1298 | + getStatus: function() {
|
| 1299 | + try {
|
| 1300 | + return this.transport.status || 0;
|
| 1301 | + } catch (e) { return 0 }
|
| 1302 | + },
|
| 1303 | +
|
| 1304 | + respondToReadyState: function(readyState) {
|
| 1305 | + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
|
| 1306 | +
|
| 1307 | + if (state == 'Complete') {
|
| 1308 | + try {
|
| 1309 | + this._complete = true;
|
| 1310 | + (this.options['on' + response.status]
|
| 1311 | + || this.options['on' + (this.success() ? 'Success' : 'Failure')]
|
| 1312 | + || Prototype.emptyFunction)(response, response.headerJSON);
|
| 1313 | + } catch (e) {
|
| 1314 | + this.dispatchException(e);
|
| 1315 | + }
|
| 1316 | +
|
| 1317 | + var contentType = response.getHeader('Content-type');
|
| 1318 | + if (this.options.evalJS == 'force'
|
| 1319 | + || (this.options.evalJS && contentType
|
| 1320 | + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
|
| 1321 | + this.evalResponse();
|
| 1322 | + }
|
| 1323 | +
|
| 1324 | + try {
|
| 1325 | + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
|
| 1326 | + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
|
| 1327 | + } catch (e) {
|
| 1328 | + this.dispatchException(e);
|
| 1329 | + }
|
| 1330 | +
|
| 1331 | + if (state == 'Complete') {
|
| 1332 | + // avoid memory leak in MSIE: clean up
|
| 1333 | + this.transport.onreadystatechange = Prototype.emptyFunction;
|
| 1334 | + }
|
| 1335 | + },
|
| 1336 | +
|
| 1337 | + getHeader: function(name) {
|
| 1338 | + try {
|
| 1339 | + return this.transport.getResponseHeader(name);
|
| 1340 | + } catch (e) { return null }
|
| 1341 | + },
|
| 1342 | +
|
| 1343 | + evalResponse: function() {
|
| 1344 | + try {
|
| 1345 | + return eval((this.transport.responseText || '').unfilterJSON());
|
| 1346 | + } catch (e) {
|
| 1347 | + this.dispatchException(e);
|
| 1348 | + }
|
| 1349 | + },
|
| 1350 | +
|
| 1351 | + dispatchException: function(exception) {
|
| 1352 | + (this.options.onException || Prototype.emptyFunction)(this, exception);
|
| 1353 | + Ajax.Responders.dispatch('onException', this, exception);
|
| 1354 | + }
|
| 1355 | +});
|
| 1356 | +
|
| 1357 | +Ajax.Request.Events =
|
| 1358 | + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
|
| 1359 | +
|
| 1360 | +Ajax.Response = Class.create({
|
| 1361 | + initialize: function(request){
|
| 1362 | + this.request = request;
|
| 1363 | + var transport = this.transport = request.transport,
|
| 1364 | + readyState = this.readyState = transport.readyState;
|
| 1365 | +
|
| 1366 | + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
|
| 1367 | + this.status = this.getStatus();
|
| 1368 | + this.statusText = this.getStatusText();
|
| 1369 | + this.responseText = String.interpret(transport.responseText);
|
| 1370 | + this.headerJSON = this._getHeaderJSON();
|
| 1371 | + }
|
| 1372 | +
|
| 1373 | + if(readyState == 4) {
|
| 1374 | + var xml = transport.responseXML;
|
| 1375 | + this.responseXML = xml === undefined ? null : xml;
|
| 1376 | + this.responseJSON = this._getResponseJSON();
|
| 1377 | + }
|
| 1378 | + },
|
| 1379 | +
|
| 1380 | + status: 0,
|
| 1381 | + statusText: '',
|
| 1382 | +
|
| 1383 | + getStatus: Ajax.Request.prototype.getStatus,
|
| 1384 | +
|
| 1385 | + getStatusText: function() {
|
| 1386 | + try {
|
| 1387 | + return this.transport.statusText || '';
|
| 1388 | + } catch (e) { return '' }
|
| 1389 | + },
|
| 1390 | +
|
| 1391 | + getHeader: Ajax.Request.prototype.getHeader,
|
| 1392 | +
|
| 1393 | + getAllHeaders: function() {
|
| 1394 | + try {
|
| 1395 | + return this.getAllResponseHeaders();
|
| 1396 | + } catch (e) { return null }
|
| 1397 | + },
|
| 1398 | +
|
| 1399 | + getResponseHeader: function(name) {
|
| 1400 | + return this.transport.getResponseHeader(name);
|
| 1401 | + },
|
| 1402 | +
|
| 1403 | + getAllResponseHeaders: function() {
|
| 1404 | + return this.transport.getAllResponseHeaders();
|
| 1405 | + },
|
| 1406 | +
|
| 1407 | + _getHeaderJSON: function() {
|
| 1408 | + var json = this.getHeader('X-JSON');
|
| 1409 | + if (!json) return null;
|
| 1410 | + json = decodeURIComponent(escape(json));
|
| 1411 | + try {
|
| 1412 | + return json.evalJSON(this.request.options.sanitizeJSON);
|
| 1413 | + } catch (e) {
|
| 1414 | + this.request.dispatchException(e);
|
| 1415 | + }
|
| 1416 | + },
|
| 1417 | +
|
| 1418 | + _getResponseJSON: function() {
|
| 1419 | + var options = this.request.options;
|
| 1420 | + if (!options.evalJSON || (options.evalJSON != 'force' &&
|
| 1421 | + !(this.getHeader('Content-type') || '').include('application/json')))
|
| 1422 | + return null;
|
| 1423 | + try {
|
| 1424 | + return this.transport.responseText.evalJSON(options.sanitizeJSON);
|
| 1425 | + } catch (e) {
|
| 1426 | + this.request.dispatchException(e);
|
| 1427 | + }
|
| 1428 | + }
|
| 1429 | +});
|
| 1430 | +
|
| 1431 | +Ajax.Updater = Class.create(Ajax.Request, {
|
| 1432 | + initialize: function($super, container, url, options) {
|
| 1433 | + this.container = {
|
| 1434 | + success: (container.success || container),
|
| 1435 | + failure: (container.failure || (container.success ? null : container))
|
| 1436 | + };
|
| 1437 | +
|
| 1438 | + options = options || { };
|
| 1439 | + var onComplete = options.onComplete;
|
| 1440 | + options.onComplete = (function(response, param) {
|
| 1441 | + this.updateContent(response.responseText);
|
| 1442 | + if (Object.isFunction(onComplete)) onComplete(response, param);
|
| 1443 | + }).bind(this);
|
| 1444 | +
|
| 1445 | + $super(url, options);
|
| 1446 | + },
|
| 1447 | +
|
| 1448 | + updateContent: function(responseText) {
|
| 1449 | + var receiver = this.container[this.success() ? 'success' : 'failure'],
|
| 1450 | + options = this.options;
|
| 1451 | +
|
| 1452 | + if (!options.evalScripts) responseText = responseText.stripScripts();
|
| 1453 | +
|
| 1454 | + if (receiver = $(receiver)) {
|
| 1455 | + if (options.insertion) {
|
| 1456 | + if (Object.isString(options.insertion)) {
|
| 1457 | + var insertion = { }; insertion[options.insertion] = responseText;
|
| 1458 | + receiver.insert(insertion);
|
| 1459 | + }
|
| 1460 | + else options.insertion(receiver, responseText);
|
| 1461 | + }
|
| 1462 | + else receiver.update(responseText);
|
| 1463 | + }
|
| 1464 | +
|
| 1465 | + if (this.success()) {
|
| 1466 | + if (this.onComplete) this.onComplete.bind(this).defer();
|
| 1467 | + }
|
| 1468 | + }
|
| 1469 | +});
|
| 1470 | +
|
| 1471 | +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
|
| 1472 | + initialize: function($super, container, url, options) {
|
| 1473 | + $super(options);
|
| 1474 | + this.onComplete = this.options.onComplete;
|
| 1475 | +
|
| 1476 | + this.frequency = (this.options.frequency || 2);
|
| 1477 | + this.decay = (this.options.decay || 1);
|
| 1478 | +
|
| 1479 | + this.updater = { };
|
| 1480 | + this.container = container;
|
| 1481 | + this.url = url;
|
| 1482 | +
|
| 1483 | + this.start();
|
| 1484 | + },
|
| 1485 | +
|
| 1486 | + start: function() {
|
| 1487 | + this.options.onComplete = this.updateComplete.bind(this);
|
| 1488 | + this.onTimerEvent();
|
| 1489 | + },
|
| 1490 | +
|
| 1491 | + stop: function() {
|
| 1492 | + this.updater.options.onComplete = undefined;
|
| 1493 | + clearTimeout(this.timer);
|
| 1494 | + (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
|
| 1495 | + },
|
| 1496 | +
|
| 1497 | + updateComplete: function(response) {
|
| 1498 | + if (this.options.decay) {
|
| 1499 | + this.decay = (response.responseText == this.lastText ?
|
| 1500 | + this.decay * this.options.decay : 1);
|
| 1501 | +
|
| 1502 | + this.lastText = response.responseText;
|
| 1503 | + }
|
| 1504 | + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
|
| 1505 | + },
|
| 1506 | +
|
| 1507 | + onTimerEvent: function() {
|
| 1508 | + this.updater = new Ajax.Updater(this.container, this.url, this.options);
|
| 1509 | + }
|
| 1510 | +});
|
| 1511 | +function $(element) {
|
| 1512 | + if (arguments.length > 1) {
|
| 1513 | + for (var i = 0, elements = [], length = arguments.length; i < length; i++)
|
| 1514 | + elements.push($(arguments[i]));
|
| 1515 | + return elements;
|
| 1516 | + }
|
| 1517 | + if (Object.isString(element))
|
| 1518 | + element = document.getElementById(element);
|
| 1519 | + return Element.extend(element);
|
| 1520 | +}
|
| 1521 | +
|
| 1522 | +if (Prototype.BrowserFeatures.XPath) {
|
| 1523 | + document._getElementsByXPath = function(expression, parentElement) {
|
| 1524 | + var results = [];
|
| 1525 | + var query = document.evaluate(expression, $(parentElement) || document,
|
| 1526 | + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
| 1527 | + for (var i = 0, length = query.snapshotLength; i < length; i++)
|
| 1528 | + results.push(Element.extend(query.snapshotItem(i)));
|
| 1529 | + return results;
|
| 1530 | + };
|
| 1531 | +}
|
| 1532 | +
|
| 1533 | +/*--------------------------------------------------------------------------*/
|
| 1534 | +
|
| 1535 | +if (!window.Node) var Node = { };
|
| 1536 | +
|
| 1537 | +if (!Node.ELEMENT_NODE) {
|
| 1538 | + // DOM level 2 ECMAScript Language Binding
|
| 1539 | + Object.extend(Node, {
|
| 1540 | + ELEMENT_NODE: 1,
|
| 1541 | + ATTRIBUTE_NODE: 2,
|
| 1542 | + TEXT_NODE: 3,
|
| 1543 | + CDATA_SECTION_NODE: 4,
|
| 1544 | + ENTITY_REFERENCE_NODE: 5,
|
| 1545 | + ENTITY_NODE: 6,
|
| 1546 | + PROCESSING_INSTRUCTION_NODE: 7,
|
| 1547 | + COMMENT_NODE: 8,
|
| 1548 | + DOCUMENT_NODE: 9,
|
| 1549 | + DOCUMENT_TYPE_NODE: 10,
|
| 1550 | + DOCUMENT_FRAGMENT_NODE: 11,
|
| 1551 | + NOTATION_NODE: 12
|
| 1552 | + });
|
| 1553 | +}
|
| 1554 | +
|
| 1555 | +(function() {
|
| 1556 | + var element = this.Element;
|
| 1557 | + this.Element = function(tagName, attributes) {
|
| 1558 | + attributes = attributes || { };
|
| 1559 | + tagName = tagName.toLowerCase();
|
| 1560 | + var cache = Element.cache;
|
| 1561 | + if (Prototype.Browser.IE && attributes.name) {
|
| 1562 | + tagName = '<' + tagName + ' name="' + attributes.name + '">';
|
| 1563 | + delete attributes.name;
|
| 1564 | + return Element.writeAttribute(document.createElement(tagName), attributes);
|
| 1565 | + }
|
| 1566 | + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
|
| 1567 | + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
|
| 1568 | + };
|
| 1569 | + Object.extend(this.Element, element || { });
|
| 1570 | +}).call(window);
|
| 1571 | +
|
| 1572 | +Element.cache = { };
|
| 1573 | +
|
| 1574 | +Element.Methods = {
|
| 1575 | + visible: function(element) {
|
| 1576 | + return $(element).style.display != 'none';
|
| 1577 | + },
|
| 1578 | +
|
| 1579 | + toggle: function(element) {
|
| 1580 | + element = $(element);
|
| 1581 | + Element[Element.visible(element) ? 'hide' : 'show'](element);
|
| 1582 | + return element;
|
| 1583 | + },
|
| 1584 | +
|
| 1585 | + hide: function(element) {
|
| 1586 | + $(element).style.display = 'none';
|
| 1587 | + return element;
|
| 1588 | + },
|
| 1589 | +
|
| 1590 | + show: function(element) {
|
| 1591 | + $(element).style.display = '';
|
| 1592 | + return element;
|
| 1593 | + },
|
| 1594 | +
|
| 1595 | + remove: function(element) {
|
| 1596 | + element = $(element);
|
| 1597 | + element.parentNode.removeChild(element);
|
| 1598 | + return element;
|
| 1599 | + },
|
| 1600 | +
|
| 1601 | + update: function(element, content) {
|
| 1602 | + element = $(element);
|
| 1603 | + if (content && content.toElement) content = content.toElement();
|
| 1604 | + if (Object.isElement(content)) return element.update().insert(content);
|
| 1605 | + content = Object.toHTML(content);
|
| 1606 | + element.innerHTML = content.stripScripts();
|
| 1607 | + content.evalScripts.bind(content).defer();
|
| 1608 | + return element;
|
| 1609 | + },
|
| 1610 | +
|
| 1611 | + replace: function(element, content) {
|
| 1612 | + element = $(element);
|
| 1613 | + if (content && content.toElement) content = content.toElement();
|
| 1614 | + else if (!Object.isElement(content)) {
|
| 1615 | + content = Object.toHTML(content);
|
| 1616 | + var range = element.ownerDocument.createRange();
|
| 1617 | + range.selectNode(element);
|
| 1618 | + content.evalScripts.bind(content).defer();
|
| 1619 | + content = range.createContextualFragment(content.stripScripts());
|
| 1620 | + }
|
| 1621 | + element.parentNode.replaceChild(content, element);
|
| 1622 | + return element;
|
| 1623 | + },
|
| 1624 | +
|
| 1625 | + insert: function(element, insertions) {
|
| 1626 | + element = $(element);
|
| 1627 | +
|
| 1628 | + if (Object.isString(insertions) || Object.isNumber(insertions) ||
|
| 1629 | + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
| 1630 | + insertions = {bottom:insertions};
|
| 1631 | +
|
| 1632 | + var content, t, range;
|
| 1633 | +
|
| 1634 | + for (position in insertions) {
|
| 1635 | + content = insertions[position];
|
| 1636 | + position = position.toLowerCase();
|
| 1637 | + t = Element._insertionTranslations[position];
|
| 1638 | +
|
| 1639 | + if (content && content.toElement) content = content.toElement();
|
| 1640 | + if (Object.isElement(content)) {
|
| 1641 | + t.insert(element, content);
|
| 1642 | + continue;
|
| 1643 | + }
|
| 1644 | +
|
| 1645 | + content = Object.toHTML(content);
|
| 1646 | +
|
| 1647 | + range = element.ownerDocument.createRange();
|
| 1648 | + t.initializeRange(element, range);
|
| 1649 | + t.insert(element, range.createContextualFragment(content.stripScripts()));
|
| 1650 | +
|
| 1651 | + content.evalScripts.bind(content).defer();
|
| 1652 | + }
|
| 1653 | +
|
| 1654 | + return element;
|
| 1655 | + },
|
| 1656 | +
|
| 1657 | + wrap: function(element, wrapper, attributes) {
|
| 1658 | + element = $(element);
|
| 1659 | + if (Object.isElement(wrapper))
|
| 1660 | + $(wrapper).writeAttribute(attributes || { });
|
| 1661 | + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
|
| 1662 | + else wrapper = new Element('div', wrapper);
|
| 1663 | + if (element.parentNode)
|
| 1664 | + element.parentNode.replaceChild(wrapper, element);
|
| 1665 | + wrapper.appendChild(element);
|
| 1666 | + return wrapper;
|
| 1667 | + },
|
| 1668 | +
|
| 1669 | + inspect: function(element) {
|
| 1670 | + element = $(element);
|
| 1671 | + var result = '<' + element.tagName.toLowerCase();
|
| 1672 | + $H({'id': 'id', 'className': 'class'}).each(function(pair) {
|
| 1673 | + var property = pair.first(), attribute = pair.last();
|
| 1674 | + var value = (element[property] || '').toString();
|
| 1675 | + if (value) result += ' ' + attribute + '=' + value.inspect(true);
|
| 1676 | + });
|
| 1677 | + return result + '>';
|
| 1678 | + },
|
| 1679 | +
|
| 1680 | + recursivelyCollect: function(element, property) {
|
| 1681 | + element = $(element);
|
| 1682 | + var elements = [];
|
| 1683 | + while (element = element[property])
|
| 1684 | + if (element.nodeType == 1)
|
| 1685 | + elements.push(Element.extend(element));
|
| 1686 | + return elements;
|
| 1687 | + },
|
| 1688 | +
|
| 1689 | + ancestors: function(element) {
|
| 1690 | + return $(element).recursivelyCollect('parentNode');
|
| 1691 | + },
|
| 1692 | +
|
| 1693 | + descendants: function(element) {
|
| 1694 | + return $A($(element).getElementsByTagName('*')).each(Element.extend);
|
| 1695 | + },
|
| 1696 | +
|
| 1697 | + firstDescendant: function(element) {
|
| 1698 | + element = $(element).firstChild;
|
| 1699 | + while (element && element.nodeType != 1) element = element.nextSibling;
|
| 1700 | + return $(element);
|
| 1701 | + },
|
| 1702 | +
|
| 1703 | + immediateDescendants: function(element) {
|
| 1704 | + if (!(element = $(element).firstChild)) return [];
|
| 1705 | + while (element && element.nodeType != 1) element = element.nextSibling;
|
| 1706 | + if (element) return [element].concat($(element).nextSiblings());
|
| 1707 | + return [];
|
| 1708 | + },
|
| 1709 | +
|
| 1710 | + previousSiblings: function(element) {
|
| 1711 | + return $(element).recursivelyCollect('previousSibling');
|
| 1712 | + },
|
| 1713 | +
|
| 1714 | + nextSiblings: function(element) {
|
| 1715 | + return $(element).recursivelyCollect('nextSibling');
|
| 1716 | + },
|
| 1717 | +
|
| 1718 | + siblings: function(element) {
|
| 1719 | + element = $(element);
|
| 1720 | + return element.previousSiblings().reverse().concat(element.nextSiblings());
|
| 1721 | + },
|
| 1722 | +
|
| 1723 | + match: function(element, selector) {
|
| 1724 | + if (Object.isString(selector))
|
| 1725 | + selector = new Selector(selector);
|
| 1726 | + return selector.match($(element));
|
| 1727 | + },
|
| 1728 | +
|
| 1729 | + up: function(element, expression, index) {
|
| 1730 | + element = $(element);
|
| 1731 | + if (arguments.length == 1) return $(element.parentNode);
|
| 1732 | + var ancestors = element.ancestors();
|
| 1733 | + return expression ? Selector.findElement(ancestors, expression, index) :
|
| 1734 | + ancestors[index || 0];
|
| 1735 | + },
|
| 1736 | +
|
| 1737 | + down: function(element, expression, index) {
|
| 1738 | + element = $(element);
|
| 1739 | + if (arguments.length == 1) return element.firstDescendant();
|
| 1740 | + var descendants = element.descendants();
|
| 1741 | + return expression ? Selector.findElement(descendants, expression, index) :
|
| 1742 | + descendants[index || 0];
|
| 1743 | + },
|
| 1744 | +
|
| 1745 | + previous: function(element, expression, index) {
|
| 1746 | + element = $(element);
|
| 1747 | + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
|
| 1748 | + var previousSiblings = element.previousSiblings();
|
| 1749 | + return expression ? Selector.findElement(previousSiblings, expression, index) :
|
| 1750 | + previousSiblings[index || 0];
|
| 1751 | + },
|
| 1752 | +
|
| 1753 | + next: function(element, expression, index) {
|
| 1754 | + element = $(element);
|
| 1755 | + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
|
| 1756 | + var nextSiblings = element.nextSiblings();
|
| 1757 | + return expression ? Selector.findElement(nextSiblings, expression, index) :
|
| 1758 | + nextSiblings[index || 0];
|
| 1759 | + },
|
| 1760 | +
|
| 1761 | + select: function() {
|
| 1762 | + var args = $A(arguments), element = $(args.shift());
|
| 1763 | + return Selector.findChildElements(element, args);
|
| 1764 | + },
|
| 1765 | +
|
| 1766 | + adjacent: function() {
|
| 1767 | + var args = $A(arguments), element = $(args.shift());
|
| 1768 | + return Selector.findChildElements(element.parentNode, args).without(element);
|
| 1769 | + },
|
| 1770 | +
|
| 1771 | + identify: function(element) {
|
| 1772 | + element = $(element);
|
| 1773 | + var id = element.readAttribute('id'), self = arguments.callee;
|
| 1774 | + if (id) return id;
|
| 1775 | + do { id = 'anonymous_element_' + self.counter++ } while ($(id));
|
| 1776 | + element.writeAttribute('id', id);
|
| 1777 | + return id;
|
| 1778 | + },
|
| 1779 | +
|
| 1780 | + readAttribute: function(element, name) {
|
| 1781 | + element = $(element);
|
| 1782 | + if (Prototype.Browser.IE) {
|
| 1783 | + var t = Element._attributeTranslations.read;
|
| 1784 | + if (t.values[name]) return t.values[name](element, name);
|
| 1785 | + if (t.names[name]) name = t.names[name];
|
| 1786 | + if (name.include(':')) {
|
| 1787 | + return (!element.attributes || !element.attributes[name]) ? null :
|
| 1788 | + element.attributes[name].value;
|
| 1789 | + }
|
| 1790 | + }
|
| 1791 | + return element.getAttribute(name);
|
| 1792 | + },
|
| 1793 | +
|
| 1794 | + writeAttribute: function(element, name, value) {
|
| 1795 | + element = $(element);
|
| 1796 | + var attributes = { }, t = Element._attributeTranslations.write;
|
| 1797 | +
|
| 1798 | + if (typeof name == 'object') attributes = name;
|
| 1799 | + else attributes[name] = value === undefined ? true : value;
|
| 1800 | +
|
| 1801 | + for (var attr in attributes) {
|
| 1802 | + var name = t.names[attr] || attr, value = attributes[attr];
|
| 1803 | + if (t.values[attr]) name = t.values[attr](element, value);
|
| 1804 | + if (value === false || value === null)
|
| 1805 | + element.removeAttribute(name);
|
| 1806 | + else if (value === true)
|
| 1807 | + element.setAttribute(name, name);
|
| 1808 | + else element.setAttribute(name, value);
|
| 1809 | + }
|
| 1810 | + return element;
|
| 1811 | + },
|
| 1812 | +
|
| 1813 | + getHeight: function(element) {
|
| 1814 | + return $(element).getDimensions().height;
|
| 1815 | + },
|
| 1816 | +
|
| 1817 | + getWidth: function(element) {
|
| 1818 | + return $(element).getDimensions().width;
|
| 1819 | + },
|
| 1820 | +
|
| 1821 | + classNames: function(element) {
|
| 1822 | + return new Element.ClassNames(element);
|
| 1823 | + },
|
| 1824 | +
|
| 1825 | + hasClassName: function(element, className) {
|
| 1826 | + if (!(element = $(element))) return;
|
| 1827 | + var elementClassName = element.className;
|
| 1828 | + return (elementClassName.length > 0 && (elementClassName == className ||
|
| 1829 | + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
|
| 1830 | + },
|
| 1831 | +
|
| 1832 | + addClassName: function(element, className) {
|
| 1833 | + if (!(element = $(element))) return;
|
| 1834 | + if (!element.hasClassName(className))
|
| 1835 | + element.className += (element.className ? ' ' : '') + className;
|
| 1836 | + return element;
|
| 1837 | + },
|
| 1838 | +
|
| 1839 | + removeClassName: function(element, className) {
|
| 1840 | + if (!(element = $(element))) return;
|
| 1841 | + element.className = element.className.replace(
|
| 1842 | + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
|
| 1843 | + return element;
|
| 1844 | + },
|
| 1845 | +
|
| 1846 | + toggleClassName: function(element, className) {
|
| 1847 | + if (!(element = $(element))) return;
|
| 1848 | + return element[element.hasClassName(className) ?
|
| 1849 | + 'removeClassName' : 'addClassName'](className);
|
| 1850 | + },
|
| 1851 | +
|
| 1852 | + // removes whitespace-only text node children
|
| 1853 | + cleanWhitespace: function(element) {
|
| 1854 | + element = $(element);
|
| 1855 | + var node = element.firstChild;
|
| 1856 | + while (node) {
|
| 1857 | + var nextNode = node.nextSibling;
|
| 1858 | + if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
|
| 1859 | + element.removeChild(node);
|
| 1860 | + node = nextNode;
|
| 1861 | + }
|
| 1862 | + return element;
|
| 1863 | + },
|
| 1864 | +
|
| 1865 | + empty: function(element) {
|
| 1866 | + return $(element).innerHTML.blank();
|
| 1867 | + },
|
| 1868 | +
|
| 1869 | + descendantOf: function(element, ancestor) {
|
| 1870 | + element = $(element), ancestor = $(ancestor);
|
| 1871 | +
|
| 1872 | + if (element.compareDocumentPosition)
|
| 1873 | + return (element.compareDocumentPosition(ancestor) & 8) === 8;
|
| 1874 | +
|
| 1875 | + if (element.sourceIndex && !Prototype.Browser.Opera) {
|
| 1876 | + var e = element.sourceIndex, a = ancestor.sourceIndex,
|
| 1877 | + nextAncestor = ancestor.nextSibling;
|
| 1878 | + if (!nextAncestor) {
|
| 1879 | + do { ancestor = ancestor.parentNode; }
|
| 1880 | + while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
|
| 1881 | + }
|
| 1882 | + if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
|
| 1883 | + }
|
| 1884 | +
|
| 1885 | + while (element = element.parentNode)
|
| 1886 | + if (element == ancestor) return true;
|
| 1887 | + return false;
|
| 1888 | + },
|
| 1889 | +
|
| 1890 | + scrollTo: function(element) {
|
| 1891 | + element = $(element);
|
| 1892 | + var pos = element.cumulativeOffset();
|
| 1893 | + window.scrollTo(pos[0], pos[1]);
|
| 1894 | + return element;
|
| 1895 | + },
|
| 1896 | +
|
| 1897 | + getStyle: function(element, style) {
|
| 1898 | + element = $(element);
|
| 1899 | + style = style == 'float' ? 'cssFloat' : style.camelize();
|
| 1900 | + var value = element.style[style];
|
| 1901 | + if (!value) {
|
| 1902 | + var css = document.defaultView.getComputedStyle(element, null);
|
| 1903 | + value = css ? css[style] : null;
|
| 1904 | + }
|
| 1905 | + if (style == 'opacity') return value ? parseFloat(value) : 1.0;
|
| 1906 | + return value == 'auto' ? null : value;
|
| 1907 | + },
|
| 1908 | +
|
| 1909 | + getOpacity: function(element) {
|
| 1910 | + return $(element).getStyle('opacity');
|
| 1911 | + },
|
| 1912 | +
|
| 1913 | + setStyle: function(element, styles) {
|
| 1914 | + element = $(element);
|
| 1915 | + var elementStyle = element.style, match;
|
| 1916 | + if (Object.isString(styles)) {
|
| 1917 | + element.style.cssText += ';' + styles;
|
| 1918 | + return styles.include('opacity') ?
|
| 1919 | + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
|
| 1920 | + }
|
| 1921 | + for (var property in styles)
|
| 1922 | + if (property == 'opacity') element.setOpacity(styles[property]);
|
| 1923 | + else
|
| 1924 | + elementStyle[(property == 'float' || property == 'cssFloat') ?
|
| 1925 | + (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
|
| 1926 | + property] = styles[property];
|
| 1927 | +
|
| 1928 | + return element;
|
| 1929 | + },
|
| 1930 | +
|
| 1931 | + setOpacity: function(element, value) {
|
| 1932 | + element = $(element);
|
| 1933 | + element.style.opacity = (value == 1 || value === '') ? '' :
|
| 1934 | + (value < 0.00001) ? 0 : value;
|
| 1935 | + return element;
|
| 1936 | + },
|
| 1937 | +
|
| 1938 | + getDimensions: function(element) {
|
| 1939 | + element = $(element);
|
| 1940 | + var display = $(element).getStyle('display');
|
| 1941 | + if (display != 'none' && display != null) // Safari bug
|
| 1942 | + return {width: element.offsetWidth, height: element.offsetHeight};
|
| 1943 | +
|
| 1944 | + // All *Width and *Height properties give 0 on elements with display none,
|
| 1945 | + // so enable the element temporarily
|
| 1946 | + var els = element.style;
|
| 1947 | + var originalVisibility = els.visibility;
|
| 1948 | + var originalPosition = els.position;
|
| 1949 | + var originalDisplay = els.display;
|
| 1950 | + els.visibility = 'hidden';
|
| 1951 | + els.position = 'absolute';
|
| 1952 | + els.display = 'block';
|
| 1953 | + var originalWidth = element.clientWidth;
|
| 1954 | + var originalHeight = element.clientHeight;
|
| 1955 | + els.display = originalDisplay;
|
| 1956 | + els.position = originalPosition;
|
| 1957 | + els.visibility = originalVisibility;
|
| 1958 | + return {width: originalWidth, height: originalHeight};
|
| 1959 | + },
|
| 1960 | +
|
| 1961 | + makePositioned: function(element) {
|
| 1962 | + element = $(element);
|
| 1963 | + var pos = Element.getStyle(element, 'position');
|
| 1964 | + if (pos == 'static' || !pos) {
|
| 1965 | + element._madePositioned = true;
|
| 1966 | + element.style.position = 'relative';
|
| 1967 | + // Opera returns the offset relative to the positioning context, when an
|
| 1968 | + // element is position relative but top and left have not been defined
|
| 1969 | + if (window.opera) {
|
| 1970 | + element.style.top = 0;
|
| 1971 | + element.style.left = 0;
|
| 1972 | + }
|
| 1973 | + }
|
| 1974 | + return element;
|
| 1975 | + },
|
| 1976 | +
|
| 1977 | + undoPositioned: function(element) {
|
| 1978 | + element = $(element);
|
| 1979 | + if (element._madePositioned) {
|
| 1980 | + element._madePositioned = undefined;
|
| 1981 | + element.style.position =
|
| 1982 | + element.style.top =
|
| 1983 | + element.style.left =
|
| 1984 | + element.style.bottom =
|
| 1985 | + element.style.right = '';
|
| 1986 | + }
|
| 1987 | + return element;
|
| 1988 | + },
|
| 1989 | +
|
| 1990 | + makeClipping: function(element) {
|
| 1991 | + element = $(element);
|
| 1992 | + if (element._overflow) return element;
|
| 1993 | + element._overflow = Element.getStyle(element, 'overflow') || 'auto';
|
| 1994 | + if (element._overflow !== 'hidden')
|
| 1995 | + element.style.overflow = 'hidden';
|
| 1996 | + return element;
|
| 1997 | + },
|
| 1998 | +
|
| 1999 | + undoClipping: function(element) {
|
| 2000 | + element = $(element);
|
| 2001 | + if (!element._overflow) return element;
|
| 2002 | + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
|
| 2003 | + element._overflow = null;
|
| 2004 | + return element;
|
| 2005 | + },
|
| 2006 | +
|
| 2007 | + cumulativeOffset: function(element) {
|
| 2008 | + var valueT = 0, valueL = 0;
|
| 2009 | + do {
|
| 2010 | + valueT += element.offsetTop || 0;
|
| 2011 | + valueL += element.offsetLeft || 0;
|
| 2012 | + element = element.offsetParent;
|
| 2013 | + } while (element);
|
| 2014 | + return Element._returnOffset(valueL, valueT);
|
| 2015 | + },
|
| 2016 | +
|
| 2017 | + positionedOffset: function(element) {
|
| 2018 | + var valueT = 0, valueL = 0;
|
| 2019 | + do {
|
| 2020 | + valueT += element.offsetTop || 0;
|
| 2021 | + valueL += element.offsetLeft || 0;
|
| 2022 | + element = element.offsetParent;
|
| 2023 | + if (element) {
|
| 2024 | + if (element.tagName == 'BODY') break;
|
| 2025 | + var p = Element.getStyle(element, 'position');
|
| 2026 | + if (p == 'relative' || p == 'absolute') break;
|
| 2027 | + }
|
| 2028 | + } while (element);
|
| 2029 | + return Element._returnOffset(valueL, valueT);
|
| 2030 | + },
|
| 2031 | +
|
| 2032 | + absolutize: function(element) {
|
| 2033 | + element = $(element);
|
| 2034 | + if (element.getStyle('position') == 'absolute') return;
|
| 2035 | + // Position.prepare(); // To be done manually by Scripty when it needs it.
|
| 2036 | +
|
| 2037 | + var offsets = element.positionedOffset();
|
| 2038 | + var top = offsets[1];
|
| 2039 | + var left = offsets[0];
|
| 2040 | + var width = element.clientWidth;
|
| 2041 | + var height = element.clientHeight;
|
| 2042 | +
|
| 2043 | + element._originalLeft = left - parseFloat(element.style.left || 0);
|
| 2044 | + element._originalTop = top - parseFloat(element.style.top || 0);
|
| 2045 | + element._originalWidth = element.style.width;
|
| 2046 | + element._originalHeight = element.style.height;
|
| 2047 | +
|
| 2048 | + element.style.position = 'absolute';
|
| 2049 | + element.style.top = top + 'px';
|
| 2050 | + element.style.left = left + 'px';
|
| 2051 | + element.style.width = width + 'px';
|
| 2052 | + element.style.height = height + 'px';
|
| 2053 | + return element;
|
| 2054 | + },
|
| 2055 | +
|
| 2056 | + relativize: function(element) {
|
| 2057 | + element = $(element);
|
| 2058 | + if (element.getStyle('position') == 'relative') return;
|
| 2059 | + // Position.prepare(); // To be done manually by Scripty when it needs it.
|
| 2060 | +
|
| 2061 | + element.style.position = 'relative';
|
| 2062 | + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
|
| 2063 | + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
|
| 2064 | +
|
| 2065 | + element.style.top = top + 'px';
|
| 2066 | + element.style.left = left + 'px';
|
| 2067 | + element.style.height = element._originalHeight;
|
| 2068 | + element.style.width = element._originalWidth;
|
| 2069 | + return element;
|
| 2070 | + },
|
| 2071 | +
|
| 2072 | + cumulativeScrollOffset: function(element) {
|
| 2073 | + var valueT = 0, valueL = 0;
|
| 2074 | + do {
|
| 2075 | + valueT += element.scrollTop || 0;
|
| 2076 | + valueL += element.scrollLeft || 0;
|
| 2077 | + element = element.parentNode;
|
| 2078 | + } while (element);
|
| 2079 | + return Element._returnOffset(valueL, valueT);
|
| 2080 | + },
|
| 2081 | +
|
| 2082 | + getOffsetParent: function(element) {
|
| 2083 | + if (element.offsetParent) return $(element.offsetParent);
|
| 2084 | + if (element == document.body) return $(element);
|
| 2085 | +
|
| 2086 | + while ((element = element.parentNode) && element != document.body)
|
| 2087 | + if (Element.getStyle(element, 'position') != 'static')
|
| 2088 | + return $(element);
|
| 2089 | +
|
| 2090 | + return $(document.body);
|
| 2091 | + },
|
| 2092 | +
|
| 2093 | + viewportOffset: function(forElement) {
|
| 2094 | + var valueT = 0, valueL = 0;
|
| 2095 | +
|
| 2096 | + var element = forElement;
|
| 2097 | + do {
|
| 2098 | + valueT += element.offsetTop || 0;
|
| 2099 | + valueL += element.offsetLeft || 0;
|
| 2100 | +
|
| 2101 | + // Safari fix
|
| 2102 | + if (element.offsetParent == document.body &&
|
| 2103 | + Element.getStyle(element, 'position') == 'absolute') break;
|
| 2104 | +
|
| 2105 | + } while (element = element.offsetParent);
|
| 2106 | +
|
| 2107 | + element = forElement;
|
| 2108 | + do {
|
| 2109 | + if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
|
| 2110 | + valueT -= element.scrollTop || 0;
|
| 2111 | + valueL -= element.scrollLeft || 0;
|
| 2112 | + }
|
| 2113 | + } while (element = element.parentNode);
|
| 2114 | +
|
| 2115 | + return Element._returnOffset(valueL, valueT);
|
| 2116 | + },
|
| 2117 | +
|
| 2118 | + clonePosition: function(element, source) {
|
| 2119 | + var options = Object.extend({
|
| 2120 | + setLeft: true,
|
| 2121 | + setTop: true,
|
| 2122 | + setWidth: true,
|
| 2123 | + setHeight: true,
|
| 2124 | + offsetTop: 0,
|
| 2125 | + offsetLeft: 0
|
| 2126 | + }, arguments[2] || { });
|
| 2127 | +
|
| 2128 | + // find page position of source
|
| 2129 | + source = $(source);
|
| 2130 | + var p = source.viewportOffset();
|
| 2131 | +
|
| 2132 | + // find coordinate system to use
|
| 2133 | + element = $(element);
|
| 2134 | + var delta = [0, 0];
|
| 2135 | + var parent = null;
|
| 2136 | + // delta [0,0] will do fine with position: fixed elements,
|
| 2137 | + // position:absolute needs offsetParent deltas
|
| 2138 | + if (Element.getStyle(element, 'position') == 'absolute') {
|
| 2139 | + parent = element.getOffsetParent();
|
| 2140 | + delta = parent.viewportOffset();
|
| 2141 | + }
|
| 2142 | +
|
| 2143 | + // correct by body offsets (fixes Safari)
|
| 2144 | + if (parent == document.body) {
|
| 2145 | + delta[0] -= document.body.offsetLeft;
|
| 2146 | + delta[1] -= document.body.offsetTop;
|
| 2147 | + }
|
| 2148 | +
|
| 2149 | + // set position
|
| 2150 | + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
|
| 2151 | + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
|
| 2152 | + if (options.setWidth) element.style.width = source.offsetWidth + 'px';
|
| 2153 | + if (options.setHeight) element.style.height = source.offsetHeight + 'px';
|
| 2154 | + return element;
|
| 2155 | + }
|
| 2156 | +};
|
| 2157 | +
|
| 2158 | +Element.Methods.identify.counter = 1;
|
| 2159 | +
|
| 2160 | +Object.extend(Element.Methods, {
|
| 2161 | + getElementsBySelector: Element.Methods.select,
|
| 2162 | + childElements: Element.Methods.immediateDescendants
|
| 2163 | +});
|
| 2164 | +
|
| 2165 | +Element._attributeTranslations = {
|
| 2166 | + write: {
|
| 2167 | + names: {
|
| 2168 | + className: 'class',
|
| 2169 | + htmlFor: 'for'
|
| 2170 | + },
|
| 2171 | + values: { }
|
| 2172 | + }
|
| 2173 | +};
|
| 2174 | +
|
| 2175 | +
|
| 2176 | +if (!document.createRange || Prototype.Browser.Opera) {
|
| 2177 | + Element.Methods.insert = function(element, insertions) {
|
| 2178 | + element = $(element);
|
| 2179 | +
|
| 2180 | + if (Object.isString(insertions) || Object.isNumber(insertions) ||
|
| 2181 | + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
| 2182 | + insertions = { bottom: insertions };
|
| 2183 | +
|
| 2184 | + var t = Element._insertionTranslations, content, position, pos, tagName;
|
| 2185 | +
|
| 2186 | + for (position in insertions) {
|
| 2187 | + content = insertions[position];
|
| 2188 | + position = position.toLowerCase();
|
| 2189 | + pos = t[position];
|
| 2190 | +
|
| 2191 | + if (content && content.toElement) content = content.toElement();
|
| 2192 | + if (Object.isElement(content)) {
|
| 2193 | + pos.insert(element, content);
|
| 2194 | + continue;
|
| 2195 | + }
|
| 2196 | +
|
| 2197 | + content = Object.toHTML(content);
|
| 2198 | + tagName = ((position == 'before' || position == 'after')
|
| 2199 | + ? element.parentNode : element).tagName.toUpperCase();
|
| 2200 | +
|
| 2201 | + if (t.tags[tagName]) {
|
| 2202 | + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
| 2203 | + if (position == 'top' || position == 'after') fragments.reverse();
|
| 2204 | + fragments.each(pos.insert.curry(element));
|
| 2205 | + }
|
| 2206 | + else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
|
| 2207 | +
|
| 2208 | + content.evalScripts.bind(content).defer();
|
| 2209 | + }
|
| 2210 | +
|
| 2211 | + return element;
|
| 2212 | + };
|
| 2213 | +}
|
| 2214 | +
|
| 2215 | +if (Prototype.Browser.Opera) {
|
| 2216 | + Element.Methods._getStyle = Element.Methods.getStyle;
|
| 2217 | + Element.Methods.getStyle = function(element, style) {
|
| 2218 | + switch(style) {
|
| 2219 | + case 'left':
|
| 2220 | + case 'top':
|
| 2221 | + case 'right':
|
| 2222 | + case 'bottom':
|
| 2223 | + if (Element._getStyle(element, 'position') == 'static') return null;
|
| 2224 | + default: return Element._getStyle(element, style);
|
| 2225 | + }
|
| 2226 | + };
|
| 2227 | + Element.Methods._readAttribute = Element.Methods.readAttribute;
|
| 2228 | + Element.Methods.readAttribute = function(element, attribute) {
|
| 2229 | + if (attribute == 'title') return element.title;
|
| 2230 | + return Element._readAttribute(element, attribute);
|
| 2231 | + };
|
| 2232 | +}
|
| 2233 | +
|
| 2234 | +else if (Prototype.Browser.IE) {
|
| 2235 | + $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
|
| 2236 | + Element.Methods[method] = Element.Methods[method].wrap(
|
| 2237 | + function(proceed, element) {
|
| 2238 | + element = $(element);
|
| 2239 | + var position = element.getStyle('position');
|
| 2240 | + if (position != 'static') return proceed(element);
|
| 2241 | + element.setStyle({ position: 'relative' });
|
| 2242 | + var value = proceed(element);
|
| 2243 | + element.setStyle({ position: position });
|
| 2244 | + return value;
|
| 2245 | + }
|
| 2246 | + );
|
| 2247 | + });
|
| 2248 | +
|
| 2249 | + Element.Methods.getStyle = function(element, style) {
|
| 2250 | + element = $(element);
|
| 2251 | + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
|
| 2252 | + var value = element.style[style];
|
| 2253 | + if (!value && element.currentStyle) value = element.currentStyle[style];
|
| 2254 | +
|
| 2255 | + if (style == 'opacity') {
|
| 2256 | + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
|
| 2257 | + if (value[1]) return parseFloat(value[1]) / 100;
|
| 2258 | + return 1.0;
|
| 2259 | + }
|
| 2260 | +
|
| 2261 | + if (value == 'auto') {
|
| 2262 | + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
|
| 2263 | + return element['offset' + style.capitalize()] + 'px';
|
| 2264 | + return null;
|
| 2265 | + }
|
| 2266 | + return value;
|
| 2267 | + };
|
| 2268 | +
|
| 2269 | + Element.Methods.setOpacity = function(element, value) {
|
| 2270 | + function stripAlpha(filter){
|
| 2271 | + return filter.replace(/alpha\([^\)]*\)/gi,'');
|
| 2272 | + }
|
| 2273 | + element = $(element);
|
| 2274 | + var currentStyle = element.currentStyle;
|
| 2275 | + if ((currentStyle && !currentStyle.hasLayout) ||
|
| 2276 | + (!currentStyle && element.style.zoom == 'normal'))
|
| 2277 | + element.style.zoom = 1;
|
| 2278 | +
|
| 2279 | + var filter = element.getStyle('filter'), style = element.style;
|
| 2280 | + if (value == 1 || value === '') {
|
| 2281 | + (filter = stripAlpha(filter)) ?
|
| 2282 | + style.filter = filter : style.removeAttribute('filter');
|
| 2283 | + return element;
|
| 2284 | + } else if (value < 0.00001) value = 0;
|
| 2285 | + style.filter = stripAlpha(filter) +
|
| 2286 | + 'alpha(opacity=' + (value * 100) + ')';
|
| 2287 | + return element;
|
| 2288 | + };
|
| 2289 | +
|
| 2290 | + Element._attributeTranslations = {
|
| 2291 | + read: {
|
| 2292 | + names: {
|
| 2293 | + 'class': 'className',
|
| 2294 | + 'for': 'htmlFor'
|
| 2295 | + },
|
| 2296 | + values: {
|
| 2297 | + _getAttr: function(element, attribute) {
|
| 2298 | + return element.getAttribute(attribute, 2);
|
| 2299 | + },
|
| 2300 | + _getAttrNode: function(element, attribute) {
|
| 2301 | + var node = element.getAttributeNode(attribute);
|
| 2302 | + return node ? node.value : "";
|
| 2303 | + },
|
| 2304 | + _getEv: function(element, attribute) {
|
| 2305 | + var attribute = element.getAttribute(attribute);
|
| 2306 | + return attribute ? attribute.toString().slice(23, -2) : null;
|
| 2307 | + },
|
| 2308 | + _flag: function(element, attribute) {
|
| 2309 | + return $(element).hasAttribute(attribute) ? attribute : null;
|
| 2310 | + },
|
| 2311 | + style: function(element) {
|
| 2312 | + return element.style.cssText.toLowerCase();
|
| 2313 | + },
|
| 2314 | + title: function(element) {
|
| 2315 | + return element.title;
|
| 2316 | + }
|
| 2317 | + }
|
| 2318 | + }
|
| 2319 | + };
|
| 2320 | +
|
| 2321 | + Element._attributeTranslations.write = {
|
| 2322 | + names: Object.clone(Element._attributeTranslations.read.names),
|
| 2323 | + values: {
|
| 2324 | + checked: function(element, value) {
|
| 2325 | + element.checked = !!value;
|
| 2326 | + },
|
| 2327 | +
|
| 2328 | + style: function(element, value) {
|
| 2329 | + element.style.cssText = value ? value : '';
|
| 2330 | + }
|
| 2331 | + }
|
| 2332 | + };
|
| 2333 | +
|
| 2334 | + Element._attributeTranslations.has = {};
|
| 2335 | +
|
| 2336 | + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
|
| 2337 | + 'encType maxLength readOnly longDesc').each(function(attr) {
|
| 2338 | + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
|
| 2339 | + Element._attributeTranslations.has[attr.toLowerCase()] = attr;
|
| 2340 | + });
|
| 2341 | +
|
| 2342 | + (function(v) {
|
| 2343 | + Object.extend(v, {
|
| 2344 | + href: v._getAttr,
|
| 2345 | + src: v._getAttr,
|
| 2346 | + type: v._getAttr,
|
| 2347 | + action: v._getAttrNode,
|
| 2348 | + disabled: v._flag,
|
| 2349 | + checked: v._flag,
|
| 2350 | + readonly: v._flag,
|
| 2351 | + multiple: v._flag,
|
| 2352 | + onload: v._getEv,
|
| 2353 | + onunload: v._getEv,
|
| 2354 | + onclick: v._getEv,
|
| 2355 | + ondblclick: v._getEv,
|
| 2356 | + onmousedown: v._getEv,
|
| 2357 | + onmouseup: v._getEv,
|
| 2358 | + onmouseover: v._getEv,
|
| 2359 | + onmousemove: v._getEv,
|
| 2360 | + onmouseout: v._getEv,
|
| 2361 | + onfocus: v._getEv,
|
| 2362 | + onblur: v._getEv,
|
| 2363 | + onkeypress: v._getEv,
|
| 2364 | + onkeydown: v._getEv,
|
| 2365 | + onkeyup: v._getEv,
|
| 2366 | + onsubmit: v._getEv,
|
| 2367 | + onreset: v._getEv,
|
| 2368 | + onselect: v._getEv,
|
| 2369 | + onchange: v._getEv
|
| 2370 | + });
|
| 2371 | + })(Element._attributeTranslations.read.values);
|
| 2372 | +}
|
| 2373 | +
|
| 2374 | +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
|
| 2375 | + Element.Methods.setOpacity = function(element, value) {
|
| 2376 | + element = $(element);
|
| 2377 | + element.style.opacity = (value == 1) ? 0.999999 :
|
| 2378 | + (value === '') ? '' : (value < 0.00001) ? 0 : value;
|
| 2379 | + return element;
|
| 2380 | + };
|
| 2381 | +}
|
| 2382 | +
|
| 2383 | +else if (Prototype.Browser.WebKit) {
|
| 2384 | + Element.Methods.setOpacity = function(element, value) {
|
| 2385 | + element = $(element);
|
| 2386 | + element.style.opacity = (value == 1 || value === '') ? '' :
|
| 2387 | + (value < 0.00001) ? 0 : value;
|
| 2388 | +
|
| 2389 | + if (value == 1)
|
| 2390 | + if(element.tagName == 'IMG' && element.width) {
|
| 2391 | + element.width++; element.width--;
|
| 2392 | + } else try {
|
| 2393 | + var n = document.createTextNode(' ');
|
| 2394 | + element.appendChild(n);
|
| 2395 | + element.removeChild(n);
|
| 2396 | + } catch (e) { }
|
| 2397 | +
|
| 2398 | + return element;
|
| 2399 | + };
|
| 2400 | +
|
| 2401 | + // Safari returns margins on body which is incorrect if the child is absolutely
|
| 2402 | + // positioned. For performance reasons, redefine Position.cumulativeOffset for
|
| 2403 | + // KHTML/WebKit only.
|
| 2404 | + Element.Methods.cumulativeOffset = function(element) {
|
| 2405 | + var valueT = 0, valueL = 0;
|
| 2406 | + do {
|
| 2407 | + valueT += element.offsetTop || 0;
|
| 2408 | + valueL += element.offsetLeft || 0;
|
| 2409 | + if (element.offsetParent == document.body)
|
| 2410 | + if (Element.getStyle(element, 'position') == 'absolute') break;
|
| 2411 | +
|
| 2412 | + element = element.offsetParent;
|
| 2413 | + } while (element);
|
| 2414 | +
|
| 2415 | + return Element._returnOffset(valueL, valueT);
|
| 2416 | + };
|
| 2417 | +}
|
| 2418 | +
|
| 2419 | +if (Prototype.Browser.IE || Prototype.Browser.Opera) {
|
| 2420 | + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
|
| 2421 | + Element.Methods.update = function(element, content) {
|
| 2422 | + element = $(element);
|
| 2423 | +
|
| 2424 | + if (content && content.toElement) content = content.toElement();
|
| 2425 | + if (Object.isElement(content)) return element.update().insert(content);
|
| 2426 | +
|
| 2427 | + content = Object.toHTML(content);
|
| 2428 | + var tagName = element.tagName.toUpperCase();
|
| 2429 | +
|
| 2430 | + if (tagName in Element._insertionTranslations.tags) {
|
| 2431 | + $A(element.childNodes).each(function(node) { element.removeChild(node) });
|
| 2432 | + Element._getContentFromAnonymousElement(tagName, content.stripScripts())
|
| 2433 | + .each(function(node) { element.appendChild(node) });
|
| 2434 | + }
|
| 2435 | + else element.innerHTML = content.stripScripts();
|
| 2436 | +
|
| 2437 | + content.evalScripts.bind(content).defer();
|
| 2438 | + return element;
|
| 2439 | + };
|
| 2440 | +}
|
| 2441 | +
|
| 2442 | +if (document.createElement('div').outerHTML) {
|
| 2443 | + Element.Methods.replace = function(element, content) {
|
| 2444 | + element = $(element);
|
| 2445 | +
|
| 2446 | + if (content && content.toElement) content = content.toElement();
|
| 2447 | + if (Object.isElement(content)) {
|
| 2448 | + element.parentNode.replaceChild(content, element);
|
| 2449 | + return element;
|
| 2450 | + }
|
| 2451 | +
|
| 2452 | + content = Object.toHTML(content);
|
| 2453 | + var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
|
| 2454 | +
|
| 2455 | + if (Element._insertionTranslations.tags[tagName]) {
|
| 2456 | + var nextSibling = element.next();
|
| 2457 | + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
| 2458 | + parent.removeChild(element);
|
| 2459 | + if (nextSibling)
|
| 2460 | + fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
|
| 2461 | + else
|
| 2462 | + fragments.each(function(node) { parent.appendChild(node) });
|
| 2463 | + }
|
| 2464 | + else element.outerHTML = content.stripScripts();
|
| 2465 | +
|
| 2466 | + content.evalScripts.bind(content).defer();
|
| 2467 | + return element;
|
| 2468 | + };
|
| 2469 | +}
|
| 2470 | +
|
| 2471 | +Element._returnOffset = function(l, t) {
|
| 2472 | + var result = [l, t];
|
| 2473 | + result.left = l;
|
| 2474 | + result.top = t;
|
| 2475 | + return result;
|
| 2476 | +};
|
| 2477 | +
|
| 2478 | +Element._getContentFromAnonymousElement = function(tagName, html) {
|
| 2479 | + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
|
| 2480 | + div.innerHTML = t[0] + html + t[1];
|
| 2481 | + t[2].times(function() { div = div.firstChild });
|
| 2482 | + return $A(div.childNodes);
|
| 2483 | +};
|
| 2484 | +
|
| 2485 | +Element._insertionTranslations = {
|
| 2486 | + before: {
|
| 2487 | + adjacency: 'beforeBegin',
|
| 2488 | + insert: function(element, node) {
|
| 2489 | + element.parentNode.insertBefore(node, element);
|
| 2490 | + },
|
| 2491 | + initializeRange: function(element, range) {
|
| 2492 | + range.setStartBefore(element);
|
| 2493 | + }
|
| 2494 | + },
|
| 2495 | + top: {
|
| 2496 | + adjacency: 'afterBegin',
|
| 2497 | + insert: function(element, node) {
|
| 2498 | + element.insertBefore(node, element.firstChild);
|
| 2499 | + },
|
| 2500 | + initializeRange: function(element, range) {
|
| 2501 | + range.selectNodeContents(element);
|
| 2502 | + range.collapse(true);
|
| 2503 | + }
|
| 2504 | + },
|
| 2505 | + bottom: {
|
| 2506 | + adjacency: 'beforeEnd',
|
| 2507 | + insert: function(element, node) {
|
| 2508 | + element.appendChild(node);
|
| 2509 | + }
|
| 2510 | + },
|
| 2511 | + after: {
|
| 2512 | + adjacency: 'afterEnd',
|
| 2513 | + insert: function(element, node) {
|
| 2514 | + element.parentNode.insertBefore(node, element.nextSibling);
|
| 2515 | + },
|
| 2516 | + initializeRange: function(element, range) {
|
| 2517 | + range.setStartAfter(element);
|
| 2518 | + }
|
| 2519 | + },
|
| 2520 | + tags: {
|
| 2521 | + TABLE: ['<table>', '</table>', 1],
|
| 2522 | + TBODY: ['<table><tbody>', '</tbody></table>', 2],
|
| 2523 | + TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
|
| 2524 | + TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
|
| 2525 | + SELECT: ['<select>', '</select>', 1]
|
| 2526 | + }
|
| 2527 | +};
|
| 2528 | +
|
| 2529 | +(function() {
|
| 2530 | + this.bottom.initializeRange = this.top.initializeRange;
|
| 2531 | + Object.extend(this.tags, {
|
| 2532 | + THEAD: this.tags.TBODY,
|
| 2533 | + TFOOT: this.tags.TBODY,
|
| 2534 | + TH: this.tags.TD
|
| 2535 | + });
|
| 2536 | +}).call(Element._insertionTranslations);
|
| 2537 | +
|
| 2538 | +Element.Methods.Simulated = {
|
| 2539 | + hasAttribute: function(element, attribute) {
|
| 2540 | + attribute = Element._attributeTranslations.has[attribute] || attribute;
|
| 2541 | + var node = $(element).getAttributeNode(attribute);
|
| 2542 | + return node && node.specified;
|
| 2543 | + }
|
| 2544 | +};
|
| 2545 | +
|
| 2546 | +Element.Methods.ByTag = { };
|
| 2547 | +
|
| 2548 | +Object.extend(Element, Element.Methods);
|
| 2549 | +
|
| 2550 | +if (!Prototype.BrowserFeatures.ElementExtensions &&
|
| 2551 | + document.createElement('div').__proto__) {
|
| 2552 | + window.HTMLElement = { };
|
| 2553 | + window.HTMLElement.prototype = document.createElement('div').__proto__;
|
| 2554 | + Prototype.BrowserFeatures.ElementExtensions = true;
|
| 2555 | +}
|
| 2556 | +
|
| 2557 | +Element.extend = (function() {
|
| 2558 | + if (Prototype.BrowserFeatures.SpecificElementExtensions)
|
| 2559 | + return Prototype.K;
|
| 2560 | +
|
| 2561 | + var Methods = { }, ByTag = Element.Methods.ByTag;
|
| 2562 | +
|
| 2563 | + var extend = Object.extend(function(element) {
|
| 2564 | + if (!element || element._extendedByPrototype ||
|
| 2565 | + element.nodeType != 1 || element == window) return element;
|
| 2566 | +
|
| 2567 | + var methods = Object.clone(Methods),
|
| 2568 | + tagName = element.tagName, property, value;
|
| 2569 | +
|
| 2570 | + // extend methods for specific tags
|
| 2571 | + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
|
| 2572 | +
|
| 2573 | + for (property in methods) {
|
| 2574 | + value = methods[property];
|
| 2575 | + if (Object.isFunction(value) && !(property in element))
|
| 2576 | + element[property] = value.methodize();
|
| 2577 | + }
|
| 2578 | +
|
| 2579 | + element._extendedByPrototype = Prototype.emptyFunction;
|
| 2580 | + return element;
|
| 2581 | +
|
| 2582 | + }, {
|
| 2583 | + refresh: function() {
|
| 2584 | + // extend methods for all tags (Safari doesn't need this)
|
| 2585 | + if (!Prototype.BrowserFeatures.ElementExtensions) {
|
| 2586 | + Object.extend(Methods, Element.Methods);
|
| 2587 | + Object.extend(Methods, Element.Methods.Simulated);
|
| 2588 | + }
|
| 2589 | + }
|
| 2590 | + });
|
| 2591 | +
|
| 2592 | + extend.refresh();
|
| 2593 | + return extend;
|
| 2594 | +})();
|
| 2595 | +
|
| 2596 | +Element.hasAttribute = function(element, attribute) {
|
| 2597 | + if (element.hasAttribute) return element.hasAttribute(attribute);
|
| 2598 | + return Element.Methods.Simulated.hasAttribute(element, attribute);
|
| 2599 | +};
|
| 2600 | +
|
| 2601 | +Element.addMethods = function(methods) {
|
| 2602 | + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
|
| 2603 | +
|
| 2604 | + if (!methods) {
|
| 2605 | + Object.extend(Form, Form.Methods);
|
| 2606 | + Object.extend(Form.Element, Form.Element.Methods);
|
| 2607 | + Object.extend(Element.Methods.ByTag, {
|
| 2608 | + "FORM": Object.clone(Form.Methods),
|
| 2609 | + "INPUT": Object.clone(Form.Element.Methods),
|
| 2610 | + "SELECT": Object.clone(Form.Element.Methods),
|
| 2611 | + "TEXTAREA": Object.clone(Form.Element.Methods)
|
| 2612 | + });
|
| 2613 | + }
|
| 2614 | +
|
| 2615 | + if (arguments.length == 2) {
|
| 2616 | + var tagName = methods;
|
| 2617 | + methods = arguments[1];
|
| 2618 | + }
|
| 2619 | +
|
| 2620 | + if (!tagName) Object.extend(Element.Methods, methods || { });
|
| 2621 | + else {
|
| 2622 | + if (Object.isArray(tagName)) tagName.each(extend);
|
| 2623 | + else extend(tagName);
|
| 2624 | + }
|
| 2625 | +
|
| 2626 | + function extend(tagName) {
|
| 2627 | + tagName = tagName.toUpperCase();
|
| 2628 | + if (!Element.Methods.ByTag[tagName])
|
| 2629 | + Element.Methods.ByTag[tagName] = { };
|
| 2630 | + Object.extend(Element.Methods.ByTag[tagName], methods);
|
| 2631 | + }
|
| 2632 | +
|
| 2633 | + function copy(methods, destination, onlyIfAbsent) {
|
| 2634 | + onlyIfAbsent = onlyIfAbsent || false;
|
| 2635 | + for (var property in methods) {
|
| 2636 | + var value = methods[property];
|
| 2637 | + if (!Object.isFunction(value)) continue;
|
| 2638 | + if (!onlyIfAbsent || !(property in destination))
|
| 2639 | + destination[property] = value.methodize();
|
| 2640 | + }
|
| 2641 | + }
|
| 2642 | +
|
| 2643 | + function findDOMClass(tagName) {
|
| 2644 | + var klass;
|
| 2645 | + var trans = {
|
| 2646 | + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
|
| 2647 | + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
|
| 2648 | + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
|
| 2649 | + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
|
| 2650 | + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
|
| 2651 | + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
|
| 2652 | + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
|
| 2653 | + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
|
| 2654 | + "FrameSet", "IFRAME": "IFrame"
|
| 2655 | + };
|
| 2656 | + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
|
| 2657 | + if (window[klass]) return window[klass];
|
| 2658 | + klass = 'HTML' + tagName + 'Element';
|
| 2659 | + if (window[klass]) return window[klass];
|
| 2660 | + klass = 'HTML' + tagName.capitalize() + 'Element';
|
| 2661 | + if (window[klass]) return window[klass];
|
| 2662 | +
|
| 2663 | + window[klass] = { };
|
| 2664 | + window[klass].prototype = document.createElement(tagName).__proto__;
|
| 2665 | + return window[klass];
|
| 2666 | + }
|
| 2667 | +
|
| 2668 | + if (F.ElementExtensions) {
|
| 2669 | + copy(Element.Methods, HTMLElement.prototype);
|
| 2670 | + copy(Element.Methods.Simulated, HTMLElement.prototype, true);
|
| 2671 | + }
|
| 2672 | +
|
| 2673 | + if (F.SpecificElementExtensions) {
|
| 2674 | + for (var tag in Element.Methods.ByTag) {
|
| 2675 | + var klass = findDOMClass(tag);
|
| 2676 | + if (Object.isUndefined(klass)) continue;
|
| 2677 | + copy(T[tag], klass.prototype);
|
| 2678 | + }
|
| 2679 | + }
|
| 2680 | +
|
| 2681 | + Object.extend(Element, Element.Methods);
|
| 2682 | + delete Element.ByTag;
|
| 2683 | +
|
| 2684 | + if (Element.extend.refresh) Element.extend.refresh();
|
| 2685 | + Element.cache = { };
|
| 2686 | +};
|
| 2687 | +
|
| 2688 | +document.viewport = {
|
| 2689 | + getDimensions: function() {
|
| 2690 | + var dimensions = { };
|
| 2691 | + $w('width height').each(function(d) {
|
| 2692 | + var D = d.capitalize();
|
| 2693 | + dimensions[d] = self['inner' + D] ||
|
| 2694 | + (document.documentElement['client' + D] || document.body['client' + D]);
|
| 2695 | + });
|
| 2696 | + return dimensions;
|
| 2697 | + },
|
| 2698 | +
|
| 2699 | + getWidth: function() {
|
| 2700 | + return this.getDimensions().width;
|
| 2701 | + },
|
| 2702 | +
|
| 2703 | + getHeight: function() {
|
| 2704 | + return this.getDimensions().height;
|
| 2705 | + },
|
| 2706 | +
|
| 2707 | + getScrollOffsets: function() {
|
| 2708 | + return Element._returnOffset(
|
| 2709 | + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
|
| 2710 | + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
|
| 2711 | + }
|
| 2712 | +};
|
| 2713 | +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
|
| 2714 | + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
|
| 2715 | + * license. Please see http://www.yui-ext.com/ for more information. */
|
| 2716 | +
|
| 2717 | +var Selector = Class.create({
|
| 2718 | + initialize: function(expression) {
|
| 2719 | + this.expression = expression.strip();
|
| 2720 | + this.compileMatcher();
|
| 2721 | + },
|
| 2722 | +
|
| 2723 | + compileMatcher: function() {
|
| 2724 | + // Selectors with namespaced attributes can't use the XPath version
|
| 2725 | + if (Prototype.BrowserFeatures.XPath && !(/(\[[\w-]*?:|:checked)/).test(this.expression))
|
| 2726 | + return this.compileXPathMatcher();
|
| 2727 | +
|
| 2728 | + var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
|
| 2729 | + c = Selector.criteria, le, p, m;
|
| 2730 | +
|
| 2731 | + if (Selector._cache[e]) {
|
| 2732 | + this.matcher = Selector._cache[e];
|
| 2733 | + return;
|
| 2734 | + }
|
| 2735 | +
|
| 2736 | + this.matcher = ["this.matcher = function(root) {",
|
| 2737 | + "var r = root, h = Selector.handlers, c = false, n;"];
|
| 2738 | +
|
| 2739 | + while (e && le != e && (/\S/).test(e)) {
|
| 2740 | + le = e;
|
| 2741 | + for (var i in ps) {
|
| 2742 | + p = ps[i];
|
| 2743 | + if (m = e.match(p)) {
|
| 2744 | + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
|
| 2745 | + new Template(c[i]).evaluate(m));
|
| 2746 | + e = e.replace(m[0], '');
|
| 2747 | + break;
|
| 2748 | + }
|
| 2749 | + }
|
| 2750 | + }
|
| 2751 | +
|
| 2752 | + this.matcher.push("return h.unique(n);\n}");
|
| 2753 | + eval(this.matcher.join('\n'));
|
| 2754 | + Selector._cache[this.expression] = this.matcher;
|
| 2755 | + },
|
| 2756 | +
|
| 2757 | + compileXPathMatcher: function() {
|
| 2758 | + var e = this.expression, ps = Selector.patterns,
|
| 2759 | + x = Selector.xpath, le, m;
|
| 2760 | +
|
| 2761 | + if (Selector._cache[e]) {
|
| 2762 | + this.xpath = Selector._cache[e]; return;
|
| 2763 | + }
|
| 2764 | +
|
| 2765 | + this.matcher = ['.//*'];
|
| 2766 | + while (e && le != e && (/\S/).test(e)) {
|
| 2767 | + le = e;
|
| 2768 | + for (var i in ps) {
|
| 2769 | + if (m = e.match(ps[i])) {
|
| 2770 | + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
|
| 2771 | + new Template(x[i]).evaluate(m));
|
| 2772 | + e = e.replace(m[0], '');
|
| 2773 | + break;
|
| 2774 | + }
|
| 2775 | + }
|
| 2776 | + }
|
| 2777 | +
|
| 2778 | + this.xpath = this.matcher.join('');
|
| 2779 | + Selector._cache[this.expression] = this.xpath;
|
| 2780 | + },
|
| 2781 | +
|
| 2782 | + findElements: function(root) {
|
| 2783 | + root = root || document;
|
| 2784 | + if (this.xpath) return document._getElementsByXPath(this.xpath, root);
|
| 2785 | + return this.matcher(root);
|
| 2786 | + },
|
| 2787 | +
|
| 2788 | + match: function(element) {
|
| 2789 | + this.tokens = [];
|
| 2790 | +
|
| 2791 | + var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
|
| 2792 | + var le, p, m;
|
| 2793 | +
|
| 2794 | + while (e && le !== e && (/\S/).test(e)) {
|
| 2795 | + le = e;
|
| 2796 | + for (var i in ps) {
|
| 2797 | + p = ps[i];
|
| 2798 | + if (m = e.match(p)) {
|
| 2799 | + // use the Selector.assertions methods unless the selector
|
| 2800 | + // is too complex.
|
| 2801 | + if (as[i]) {
|
| 2802 | + this.tokens.push([i, Object.clone(m)]);
|
| 2803 | + e = e.replace(m[0], '');
|
| 2804 | + } else {
|
| 2805 | + // reluctantly do a document-wide search
|
| 2806 | + // and look for a match in the array
|
| 2807 | + return this.findElements(document).include(element);
|
| 2808 | + }
|
| 2809 | + }
|
| 2810 | + }
|
| 2811 | + }
|
| 2812 | +
|
| 2813 | + var match = true, name, matches;
|
| 2814 | + for (var i = 0, token; token = this.tokens[i]; i++) {
|
| 2815 | + name = token[0], matches = token[1];
|
| 2816 | + if (!Selector.assertions[name](element, matches)) {
|
| 2817 | + match = false; break;
|
| 2818 | + }
|
| 2819 | + }
|
| 2820 | +
|
| 2821 | + return match;
|
| 2822 | + },
|
| 2823 | +
|
| 2824 | + toString: function() {
|
| 2825 | + return this.expression;
|
| 2826 | + },
|
| 2827 | +
|
| 2828 | + inspect: function() {
|
| 2829 | + return "#<Selector:" + this.expression.inspect() + ">";
|
| 2830 | + }
|
| 2831 | +});
|
| 2832 | +
|
| 2833 | +Object.extend(Selector, {
|
| 2834 | + _cache: { },
|
| 2835 | +
|
| 2836 | + xpath: {
|
| 2837 | + descendant: "//*",
|
| 2838 | + child: "/*",
|
| 2839 | + adjacent: "/following-sibling::*[1]",
|
| 2840 | + laterSibling: '/following-sibling::*',
|
| 2841 | + tagName: function(m) {
|
| 2842 | + if (m[1] == '*') return '';
|
| 2843 | + return "[local-name()='" + m[1].toLowerCase() +
|
| 2844 | + "' or local-name()='" + m[1].toUpperCase() + "']";
|
| 2845 | + },
|
| 2846 | + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
|
| 2847 | + id: "[@id='#{1}']",
|
| 2848 | + attrPresence: "[@#{1}]",
|
| 2849 | + attr: function(m) {
|
| 2850 | + m[3] = m[5] || m[6];
|
| 2851 | + return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
|
| 2852 | + },
|
| 2853 | + pseudo: function(m) {
|
| 2854 | + var h = Selector.xpath.pseudos[m[1]];
|
| 2855 | + if (!h) return '';
|
| 2856 | + if (Object.isFunction(h)) return h(m);
|
| 2857 | + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
|
| 2858 | + },
|
| 2859 | + operators: {
|
| 2860 | + '=': "[@#{1}='#{3}']",
|
| 2861 | + '!=': "[@#{1}!='#{3}']",
|
| 2862 | + '^=': "[starts-with(@#{1}, '#{3}')]",
|
| 2863 | + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
|
| 2864 | + '*=': "[contains(@#{1}, '#{3}')]",
|
| 2865 | + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
|
| 2866 | + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
|
| 2867 | + },
|
| 2868 | + pseudos: {
|
| 2869 | + 'first-child': '[not(preceding-sibling::*)]',
|
| 2870 | + 'last-child': '[not(following-sibling::*)]',
|
| 2871 | + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
|
| 2872 | + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
|
| 2873 | + 'checked': "[@checked]",
|
| 2874 | + 'disabled': "[@disabled]",
|
| 2875 | + 'enabled': "[not(@disabled)]",
|
| 2876 | + 'not': function(m) {
|
| 2877 | + var e = m[6], p = Selector.patterns,
|
| 2878 | + x = Selector.xpath, le, m, v;
|
| 2879 | +
|
| 2880 | + var exclusion = [];
|
| 2881 | + while (e && le != e && (/\S/).test(e)) {
|
| 2882 | + le = e;
|
| 2883 | + for (var i in p) {
|
| 2884 | + if (m = e.match(p[i])) {
|
| 2885 | + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
|
| 2886 | + exclusion.push("(" + v.substring(1, v.length - 1) + ")");
|
| 2887 | + e = e.replace(m[0], '');
|
| 2888 | + break;
|
| 2889 | + }
|
| 2890 | + }
|
| 2891 | + }
|
| 2892 | + return "[not(" + exclusion.join(" and ") + ")]";
|
| 2893 | + },
|
| 2894 | + 'nth-child': function(m) {
|
| 2895 | + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
|
| 2896 | + },
|
| 2897 | + 'nth-last-child': function(m) {
|
| 2898 | + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
|
| 2899 | + },
|
| 2900 | + 'nth-of-type': function(m) {
|
| 2901 | + return Selector.xpath.pseudos.nth("position() ", m);
|
| 2902 | + },
|
| 2903 | + 'nth-last-of-type': function(m) {
|
| 2904 | + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
|
| 2905 | + },
|
| 2906 | + 'first-of-type': function(m) {
|
| 2907 | + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
|
| 2908 | + },
|
| 2909 | + 'last-of-type': function(m) {
|
| 2910 | + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
|
| 2911 | + },
|
| 2912 | + 'only-of-type': function(m) {
|
| 2913 | + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
|
| 2914 | + },
|
| 2915 | + nth: function(fragment, m) {
|
| 2916 | + var mm, formula = m[6], predicate;
|
| 2917 | + if (formula == 'even') formula = '2n+0';
|
| 2918 | + if (formula == 'odd') formula = '2n+1';
|
| 2919 | + if (mm = formula.match(/^(\d+)$/)) // digit only
|
| 2920 | + return '[' + fragment + "= " + mm[1] + ']';
|
| 2921 | + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
|
| 2922 | + if (mm[1] == "-") mm[1] = -1;
|
| 2923 | + var a = mm[1] ? Number(mm[1]) : 1;
|
| 2924 | + var b = mm[2] ? Number(mm[2]) : 0;
|
| 2925 | + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
|
| 2926 | + "((#{fragment} - #{b}) div #{a} >= 0)]";
|
| 2927 | + return new Template(predicate).evaluate({
|
| 2928 | + fragment: fragment, a: a, b: b });
|
| 2929 | + }
|
| 2930 | + }
|
| 2931 | + }
|
| 2932 | + },
|
| 2933 | +
|
| 2934 | + criteria: {
|
| 2935 | + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
| 2936 | + className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
| 2937 | + id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
| 2938 | + attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
|
| 2939 | + attr: function(m) {
|
| 2940 | + m[3] = (m[5] || m[6]);
|
| 2941 | + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
|
| 2942 | + },
|
| 2943 | + pseudo: function(m) {
|
| 2944 | + if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
|
| 2945 | + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
|
| 2946 | + },
|
| 2947 | + descendant: 'c = "descendant";',
|
| 2948 | + child: 'c = "child";',
|
| 2949 | + adjacent: 'c = "adjacent";',
|
| 2950 | + laterSibling: 'c = "laterSibling";'
|
| 2951 | + },
|
| 2952 | +
|
| 2953 | + patterns: {
|
| 2954 | + // combinators must be listed first
|
| 2955 | + // (and descendant needs to be last combinator)
|
| 2956 | + laterSibling: /^\s*~\s*/,
|
| 2957 | + child: /^\s*>\s*/,
|
| 2958 | + adjacent: /^\s*\+\s*/,
|
| 2959 | + descendant: /^\s/,
|
| 2960 | +
|
| 2961 | + // selectors follow
|
| 2962 | + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
|
| 2963 | + id: /^#([\w\-\*]+)(\b|$)/,
|
| 2964 | + className: /^\.([\w\-\*]+)(\b|$)/,
|
| 2965 | + pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
|
| 2966 | + attrPresence: /^\[([\w]+)\]/,
|
| 2967 | + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
|
| 2968 | + },
|
| 2969 | +
|
| 2970 | + // for Selector.match and Element#match
|
| 2971 | + assertions: {
|
| 2972 | + tagName: function(element, matches) {
|
| 2973 | + return matches[1].toUpperCase() == element.tagName.toUpperCase();
|
| 2974 | + },
|
| 2975 | +
|
| 2976 | + className: function(element, matches) {
|
| 2977 | + return Element.hasClassName(element, matches[1]);
|
| 2978 | + },
|
| 2979 | +
|
| 2980 | + id: function(element, matches) {
|
| 2981 | + return element.id === matches[1];
|
| 2982 | + },
|
| 2983 | +
|
| 2984 | + attrPresence: function(element, matches) {
|
| 2985 | + return Element.hasAttribute(element, matches[1]);
|
| 2986 | + },
|
| 2987 | +
|
| 2988 | + attr: function(element, matches) {
|
| 2989 | + var nodeValue = Element.readAttribute(element, matches[1]);
|
| 2990 | + return Selector.operators[matches[2]](nodeValue, matches[3]);
|
| 2991 | + }
|
| 2992 | + },
|
| 2993 | +
|
| 2994 | + handlers: {
|
| 2995 | + // UTILITY FUNCTIONS
|
| 2996 | + // joins two collections
|
| 2997 | + concat: function(a, b) {
|
| 2998 | + for (var i = 0, node; node = b[i]; i++)
|
| 2999 | + a.push(node);
|
| 3000 | + return a;
|
| 3001 | + },
|
| 3002 | +
|
| 3003 | + // marks an array of nodes for counting
|
| 3004 | + mark: function(nodes) {
|
| 3005 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3006 | + node._counted = true;
|
| 3007 | + return nodes;
|
| 3008 | + },
|
| 3009 | +
|
| 3010 | + unmark: function(nodes) {
|
| 3011 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3012 | + node._counted = undefined;
|
| 3013 | + return nodes;
|
| 3014 | + },
|
| 3015 | +
|
| 3016 | + // mark each child node with its position (for nth calls)
|
| 3017 | + // "ofType" flag indicates whether we're indexing for nth-of-type
|
| 3018 | + // rather than nth-child
|
| 3019 | + index: function(parentNode, reverse, ofType) {
|
| 3020 | + parentNode._counted = true;
|
| 3021 | + if (reverse) {
|
| 3022 | + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
|
| 3023 | + var node = nodes[i];
|
| 3024 | + if (node.nodeType == 1 && (!ofType || (node._counted==true))) node.nodeIndex = j++;
|
| 3025 | + }
|
| 3026 | + } else {
|
| 3027 | + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
|
| 3028 | + if (node.nodeType == 1 && (!ofType || (node._counted==true))) node.nodeIndex = j++;
|
| 3029 | + }
|
| 3030 | + },
|
| 3031 | +
|
| 3032 | + // filters out duplicates and extends all nodes
|
| 3033 | + unique: function(nodes) {
|
| 3034 | + if (nodes.length == 0) return nodes;
|
| 3035 | + var results = [], n;
|
| 3036 | + for (var i = 0, l = nodes.length; i < l; i++)
|
| 3037 | + if ((n = nodes[i])._counted != true) {
|
| 3038 | + n._counted = true;
|
| 3039 | + results.push(Element.extend(n));
|
| 3040 | + }
|
| 3041 | + return Selector.handlers.unmark(results);
|
| 3042 | + },
|
| 3043 | +
|
| 3044 | + // COMBINATOR FUNCTIONS
|
| 3045 | + descendant: function(nodes) {
|
| 3046 | + var h = Selector.handlers;
|
| 3047 | + for (var i = 0, results = [], node; node = nodes[i]; i++)
|
| 3048 | + h.concat(results, node.getElementsByTagName('*'));
|
| 3049 | + return results;
|
| 3050 | + },
|
| 3051 | +
|
| 3052 | + child: function(nodes) {
|
| 3053 | + var h = Selector.handlers;
|
| 3054 | + for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
| 3055 | + for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
|
| 3056 | + if (child.nodeType == 1 && child.tagName != '!') results.push(child);
|
| 3057 | + }
|
| 3058 | + return results;
|
| 3059 | + },
|
| 3060 | +
|
| 3061 | + adjacent: function(nodes) {
|
| 3062 | + for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
| 3063 | + var next = this.nextElementSibling(node);
|
| 3064 | + if (next) results.push(next);
|
| 3065 | + }
|
| 3066 | + return results;
|
| 3067 | + },
|
| 3068 | +
|
| 3069 | + laterSibling: function(nodes) {
|
| 3070 | + var h = Selector.handlers;
|
| 3071 | + for (var i = 0, results = [], node; node = nodes[i]; i++)
|
| 3072 | + h.concat(results, Element.nextSiblings(node));
|
| 3073 | + return results;
|
| 3074 | + },
|
| 3075 | +
|
| 3076 | + nextElementSibling: function(node) {
|
| 3077 | + while (node = node.nextSibling)
|
| 3078 | + if (node.nodeType == 1) return node;
|
| 3079 | + return null;
|
| 3080 | + },
|
| 3081 | +
|
| 3082 | + previousElementSibling: function(node) {
|
| 3083 | + while (node = node.previousSibling)
|
| 3084 | + if (node.nodeType == 1) return node;
|
| 3085 | + return null;
|
| 3086 | + },
|
| 3087 | +
|
| 3088 | + // TOKEN FUNCTIONS
|
| 3089 | + tagName: function(nodes, root, tagName, combinator) {
|
| 3090 | + tagName = tagName.toUpperCase();
|
| 3091 | + var results = [], h = Selector.handlers;
|
| 3092 | + if (nodes) {
|
| 3093 | + if (combinator) {
|
| 3094 | + // fastlane for ordinary descendant combinators
|
| 3095 | + if (combinator == "descendant") {
|
| 3096 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3097 | + h.concat(results, node.getElementsByTagName(tagName));
|
| 3098 | + return results;
|
| 3099 | + } else nodes = this[combinator](nodes);
|
| 3100 | + if (tagName == "*") return nodes;
|
| 3101 | + }
|
| 3102 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3103 | + if (node.tagName.toUpperCase() == tagName) results.push(node);
|
| 3104 | + return results;
|
| 3105 | + } else return root.getElementsByTagName(tagName);
|
| 3106 | + },
|
| 3107 | +
|
| 3108 | + id: function(nodes, root, id, combinator) {
|
| 3109 | + var targetNode = $(id), h = Selector.handlers;
|
| 3110 | + if (!targetNode) return [];
|
| 3111 | + if (!nodes && root == document) return [targetNode];
|
| 3112 | + if (nodes) {
|
| 3113 | + if (combinator) {
|
| 3114 | + if (combinator == 'child') {
|
| 3115 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3116 | + if (targetNode.parentNode == node) return [targetNode];
|
| 3117 | + } else if (combinator == 'descendant') {
|
| 3118 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3119 | + if (Element.descendantOf(targetNode, node)) return [targetNode];
|
| 3120 | + } else if (combinator == 'adjacent') {
|
| 3121 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3122 | + if (Selector.handlers.previousElementSibling(targetNode) == node)
|
| 3123 | + return [targetNode];
|
| 3124 | + } else nodes = h[combinator](nodes);
|
| 3125 | + }
|
| 3126 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3127 | + if (node == targetNode) return [targetNode];
|
| 3128 | + return [];
|
| 3129 | + }
|
| 3130 | + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
|
| 3131 | + },
|
| 3132 | +
|
| 3133 | + className: function(nodes, root, className, combinator) {
|
| 3134 | + if (nodes && combinator) nodes = this[combinator](nodes);
|
| 3135 | + return Selector.handlers.byClassName(nodes, root, className);
|
| 3136 | + },
|
| 3137 | +
|
| 3138 | + byClassName: function(nodes, root, className) {
|
| 3139 | + if (!nodes) nodes = Selector.handlers.descendant([root]);
|
| 3140 | + var needle = ' ' + className + ' ';
|
| 3141 | + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
|
| 3142 | + nodeClassName = node.className;
|
| 3143 | + if (nodeClassName.length == 0) continue;
|
| 3144 | + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
|
| 3145 | + results.push(node);
|
| 3146 | + }
|
| 3147 | + return results;
|
| 3148 | + },
|
| 3149 | +
|
| 3150 | + attrPresence: function(nodes, root, attr) {
|
| 3151 | + if (!nodes) nodes = root.getElementsByTagName("*");
|
| 3152 | + var results = [];
|
| 3153 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3154 | + if (Element.hasAttribute(node, attr)) results.push(node);
|
| 3155 | + return results;
|
| 3156 | + },
|
| 3157 | +
|
| 3158 | + attr: function(nodes, root, attr, value, operator) {
|
| 3159 | + if (!nodes) nodes = root.getElementsByTagName("*");
|
| 3160 | + var handler = Selector.operators[operator], results = [];
|
| 3161 | + for (var i = 0, node; node = nodes[i]; i++) {
|
| 3162 | + var nodeValue = Element.readAttribute(node, attr);
|
| 3163 | + if (nodeValue === null) continue;
|
| 3164 | + if (handler(nodeValue, value)) results.push(node);
|
| 3165 | + }
|
| 3166 | + return results;
|
| 3167 | + },
|
| 3168 | +
|
| 3169 | + pseudo: function(nodes, name, value, root, combinator) {
|
| 3170 | + if (nodes && combinator) nodes = this[combinator](nodes);
|
| 3171 | + if (!nodes) nodes = root.getElementsByTagName("*");
|
| 3172 | + return Selector.pseudos[name](nodes, value, root);
|
| 3173 | + }
|
| 3174 | + },
|
| 3175 | +
|
| 3176 | + pseudos: {
|
| 3177 | + 'first-child': function(nodes, value, root) {
|
| 3178 | + for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
| 3179 | + if (Selector.handlers.previousElementSibling(node)) continue;
|
| 3180 | + results.push(node);
|
| 3181 | + }
|
| 3182 | + return results;
|
| 3183 | + },
|
| 3184 | + 'last-child': function(nodes, value, root) {
|
| 3185 | + for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
| 3186 | + if (Selector.handlers.nextElementSibling(node)) continue;
|
| 3187 | + results.push(node);
|
| 3188 | + }
|
| 3189 | + return results;
|
| 3190 | + },
|
| 3191 | + 'only-child': function(nodes, value, root) {
|
| 3192 | + var h = Selector.handlers;
|
| 3193 | + for (var i = 0, results = [], node; node = nodes[i]; i++)
|
| 3194 | + if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
|
| 3195 | + results.push(node);
|
| 3196 | + return results;
|
| 3197 | + },
|
| 3198 | + 'nth-child': function(nodes, formula, root) {
|
| 3199 | + return Selector.pseudos.nth(nodes, formula, root);
|
| 3200 | + },
|
| 3201 | + 'nth-last-child': function(nodes, formula, root) {
|
| 3202 | + return Selector.pseudos.nth(nodes, formula, root, true);
|
| 3203 | + },
|
| 3204 | + 'nth-of-type': function(nodes, formula, root) {
|
| 3205 | + return Selector.pseudos.nth(nodes, formula, root, false, true);
|
| 3206 | + },
|
| 3207 | + 'nth-last-of-type': function(nodes, formula, root) {
|
| 3208 | + return Selector.pseudos.nth(nodes, formula, root, true, true);
|
| 3209 | + },
|
| 3210 | + 'first-of-type': function(nodes, formula, root) {
|
| 3211 | + return Selector.pseudos.nth(nodes, "1", root, false, true);
|
| 3212 | + },
|
| 3213 | + 'last-of-type': function(nodes, formula, root) {
|
| 3214 | + return Selector.pseudos.nth(nodes, "1", root, true, true);
|
| 3215 | + },
|
| 3216 | + 'only-of-type': function(nodes, formula, root) {
|
| 3217 | + var p = Selector.pseudos;
|
| 3218 | + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
|
| 3219 | + },
|
| 3220 | +
|
| 3221 | + // handles the an+b logic
|
| 3222 | + getIndices: function(a, b, total) {
|
| 3223 | + if (a == 0) return b > 0 ? [b] : [];
|
| 3224 | + return $R(1, total).inject([], function(memo, i) {
|
| 3225 | + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
|
| 3226 | + return memo;
|
| 3227 | + });
|
| 3228 | + },
|
| 3229 | +
|
| 3230 | + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
|
| 3231 | + nth: function(nodes, formula, root, reverse, ofType) {
|
| 3232 | + if (nodes.length == 0) return [];
|
| 3233 | + if (formula == 'even') formula = '2n+0';
|
| 3234 | + if (formula == 'odd') formula = '2n+1';
|
| 3235 | + var h = Selector.handlers, results = [], indexed = [], m;
|
| 3236 | + h.mark(nodes);
|
| 3237 | + for (var i = 0, node; node = nodes[i]; i++) {
|
| 3238 | + if (node.parentNode._counted != true) {
|
| 3239 | + h.index(node.parentNode, reverse, ofType);
|
| 3240 | + indexed.push(node.parentNode);
|
| 3241 | + }
|
| 3242 | + }
|
| 3243 | + if (formula.match(/^\d+$/)) { // just a number
|
| 3244 | + formula = Number(formula);
|
| 3245 | + for (var i = 0, node; node = nodes[i]; i++)
|
| 3246 | + if (node.nodeIndex == formula) results.push(node);
|
| 3247 | + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
|
| 3248 | + if (m[1] == "-") m[1] = -1;
|
| 3249 | + var a = m[1] ? Number(m[1]) : 1;
|
| 3250 | + var b = m[2] ? Number(m[2]) : 0;
|
| 3251 | + var indices = Selector.pseudos.getIndices(a, b, nodes.length);
|
| 3252 | + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
|
| 3253 | + for (var j = 0; j < l; j++)
|
| 3254 | + if (node.nodeIndex == indices[j]) results.push(node);
|
| 3255 | + }
|
| 3256 | + }
|
| 3257 | + h.unmark(nodes);
|
| 3258 | + h.unmark(indexed);
|
| 3259 | + return results;
|
| 3260 | + },
|
| 3261 | +
|
| 3262 | + 'empty': function(nodes, value, root) {
|
| 3263 | + for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
| 3264 | + // IE treats comments as element nodes
|
| 3265 | + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
|
| 3266 | + results.push(node);
|
| 3267 | + }
|
| 3268 | + return results;
|
| 3269 | + },
|
| 3270 | +
|
| 3271 | + 'not': function(nodes, selector, root) {
|
| 3272 | + var h = Selector.handlers, selectorType, m;
|
| 3273 | + var exclusions = new Selector(selector).findElements(root);
|
| 3274 | + h.mark(exclusions);
|
| 3275 | + for (var i = 0, results = [], node; node = nodes[i]; i++)
|
| 3276 | + if (node._counted!=true) results.push(node);
|
| 3277 | + h.unmark(exclusions);
|
| 3278 | + return results;
|
| 3279 | + },
|
| 3280 | +
|
| 3281 | + 'enabled': function(nodes, value, root) {
|
| 3282 | + for (var i = 0, results = [], node; node = nodes[i]; i++)
|
| 3283 | + if (!node.disabled) results.push(node);
|
| 3284 | + return results;
|
| 3285 | + },
|
| 3286 | +
|
| 3287 | + 'disabled': function(nodes, value, root) {
|
| 3288 | + for (var i = 0, results = [], node; node = nodes[i]; i++)
|
| 3289 | + if (node.disabled) results.push(node);
|
| 3290 | + return results;
|
| 3291 | + },
|
| 3292 | +
|
| 3293 | + 'checked': function(nodes, value, root) {
|
| 3294 | + for (var i = 0, results = [], node; node = nodes[i]; i++)
|
| 3295 | + if (node.checked) results.push(node);
|
| 3296 | + return results;
|
| 3297 | + }
|
| 3298 | + },
|
| 3299 | +
|
| 3300 | + operators: {
|
| 3301 | + '=': function(nv, v) { return nv == v; },
|
| 3302 | + '!=': function(nv, v) { return nv != v; },
|
| 3303 | + '^=': function(nv, v) { return nv.startsWith(v); },
|
| 3304 | + '$=': function(nv, v) { return nv.endsWith(v); },
|
| 3305 | + '*=': function(nv, v) { return nv.include(v); },
|
| 3306 | + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
|
| 3307 | + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
|
| 3308 | + },
|
| 3309 | +
|
| 3310 | + matchElements: function(elements, expression) {
|
| 3311 | + var matches = new Selector(expression).findElements(), h = Selector.handlers;
|
| 3312 | + h.mark(matches);
|
| 3313 | + for (var i = 0, results = [], element; element = elements[i]; i++)
|
| 3314 | + //TS: does not work in IE: if (element._counted) results.push(element);
|
| 3315 | + if (element._counted == true) results.push(element);
|
| 3316 | + h.unmark(matches);
|
| 3317 | + return results;
|
| 3318 | + },
|
| 3319 | +
|
| 3320 | + findElement: function(elements, expression, index) {
|
| 3321 | + if (Object.isNumber(expression)) {
|
| 3322 | + index = expression; expression = false;
|
| 3323 | + }
|
| 3324 | + return Selector.matchElements(elements, expression || '*')[index || 0];
|
| 3325 | + },
|
| 3326 | +
|
| 3327 | + findChildElements: function(element, expressions) {
|
| 3328 | + var exprs = expressions.join(','), expressions = [];
|
| 3329 | + exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
| 3330 | + expressions.push(m[1].strip());
|
| 3331 | + });
|
| 3332 | + var results = [], h = Selector.handlers;
|
| 3333 | + for (var i = 0, l = expressions.length, selector; i < l; i++) {
|
| 3334 | + selector = new Selector(expressions[i].strip());
|
| 3335 | + h.concat(results, selector.findElements(element));
|
| 3336 | + }
|
| 3337 | + return (l > 1) ? h.unique(results) : results;
|
| 3338 | + }
|
| 3339 | +});
|
| 3340 | +
|
| 3341 | +function $$() {
|
| 3342 | + return Selector.findChildElements(document, $A(arguments));
|
| 3343 | +}
|
| 3344 | +var Form = {
|
| 3345 | + reset: function(form) {
|
| 3346 | + $(form).reset();
|
| 3347 | + return form;
|
| 3348 | + },
|
| 3349 | +
|
| 3350 | + serializeElements: function(elements, options) {
|
| 3351 | + if (typeof options != 'object') options = { hash: !!options };
|
| 3352 | + else if (options.hash === undefined) options.hash = true;
|
| 3353 | + var key, value, submitted = false, submit = options.submit;
|
| 3354 | +
|
| 3355 | + var data = elements.inject({ }, function(result, element) {
|
| 3356 | + if (!element.disabled && element.name) {
|
| 3357 | + key = element.name; value = $(element).getValue();
|
| 3358 | + if (value != null && (element.type != 'submit' || (!submitted &&
|
| 3359 | + submit !== false && (!submit || key == submit) && (submitted = true)))) {
|
| 3360 | + if (key in result) {
|
| 3361 | + // a key is already present; construct an array of values
|
| 3362 | + if (!Object.isArray(result[key])) result[key] = [result[key]];
|
| 3363 | + result[key].push(value);
|
| 3364 | + }
|
| 3365 | + else result[key] = value;
|
| 3366 | + }
|
| 3367 | + }
|
| 3368 | + return result;
|
| 3369 | + });
|
| 3370 | +
|
| 3371 | + return options.hash ? data : Object.toQueryString(data);
|
| 3372 | + }
|
| 3373 | +};
|
| 3374 | +
|
| 3375 | +Form.Methods = {
|
| 3376 | + serialize: function(form, options) {
|
| 3377 | + return Form.serializeElements(Form.getElements(form), options);
|
| 3378 | + },
|
| 3379 | +
|
| 3380 | + getElements: function(form) {
|
| 3381 | + return $A($(form).getElementsByTagName('*')).inject([],
|
| 3382 | + function(elements, child) {
|
| 3383 | + if (Form.Element.Serializers[child.tagName.toLowerCase()])
|
| 3384 | + elements.push(Element.extend(child));
|
| 3385 | + return elements;
|
| 3386 | + }
|
| 3387 | + );
|
| 3388 | + },
|
| 3389 | +
|
| 3390 | + getInputs: function(form, typeName, name) {
|
| 3391 | + form = $(form);
|
| 3392 | + var inputs = form.getElementsByTagName('input');
|
| 3393 | +
|
| 3394 | + if (!typeName && !name) return $A(inputs).map(Element.extend);
|
| 3395 | +
|
| 3396 | + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
|
| 3397 | + var input = inputs[i];
|
| 3398 | + if ((typeName && input.type != typeName) || (name && input.name != name))
|
| 3399 | + continue;
|
| 3400 | + matchingInputs.push(Element.extend(input));
|
| 3401 | + }
|
| 3402 | +
|
| 3403 | + return matchingInputs;
|
| 3404 | + },
|
| 3405 | +
|
| 3406 | + disable: function(form) {
|
| 3407 | + form = $(form);
|
| 3408 | + Form.getElements(form).invoke('disable');
|
| 3409 | + return form;
|
| 3410 | + },
|
| 3411 | +
|
| 3412 | + enable: function(form) {
|
| 3413 | + form = $(form);
|
| 3414 | + Form.getElements(form).invoke('enable');
|
| 3415 | + return form;
|
| 3416 | + },
|
| 3417 | +
|
| 3418 | + findFirstElement: function(form) {
|
| 3419 | + var elements = $(form).getElements().findAll(function(element) {
|
| 3420 | + return 'hidden' != element.type && !element.disabled;
|
| 3421 | + });
|
| 3422 | + var firstByIndex = elements.findAll(function(element) {
|
| 3423 | + return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
|
| 3424 | + }).sortBy(function(element) { return element.tabIndex }).first();
|
| 3425 | +
|
| 3426 | + return firstByIndex ? firstByIndex : elements.find(function(element) {
|
| 3427 | + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
|
| 3428 | + });
|
| 3429 | + },
|
| 3430 | +
|
| 3431 | + focusFirstElement: function(form) {
|
| 3432 | + form = $(form);
|
| 3433 | + form.findFirstElement().activate();
|
| 3434 | + return form;
|
| 3435 | + },
|
| 3436 | +
|
| 3437 | + request: function(form, options) {
|
| 3438 | + form = $(form), options = Object.clone(options || { });
|
| 3439 | +
|
| 3440 | + var params = options.parameters, action = form.readAttribute('action') || '';
|
| 3441 | + if (action.blank()) action = window.location.href;
|
| 3442 | + options.parameters = form.serialize(true);
|
| 3443 | +
|
| 3444 | + if (params) {
|
| 3445 | + if (Object.isString(params)) params = params.toQueryParams();
|
| 3446 | + Object.extend(options.parameters, params);
|
| 3447 | + }
|
| 3448 | +
|
| 3449 | + if (form.hasAttribute('method') && !options.method)
|
| 3450 | + options.method = form.method;
|
| 3451 | +
|
| 3452 | + return new Ajax.Request(action, options);
|
| 3453 | + }
|
| 3454 | +};
|
| 3455 | +
|
| 3456 | +/*--------------------------------------------------------------------------*/
|
| 3457 | +
|
| 3458 | +Form.Element = {
|
| 3459 | + focus: function(element) {
|
| 3460 | + $(element).focus();
|
| 3461 | + return element;
|
| 3462 | + },
|
| 3463 | +
|
| 3464 | + select: function(element) {
|
| 3465 | + $(element).select();
|
| 3466 | + return element;
|
| 3467 | + }
|
| 3468 | +};
|
| 3469 | +
|
| 3470 | +Form.Element.Methods = {
|
| 3471 | + serialize: function(element) {
|
| 3472 | + element = $(element);
|
| 3473 | + if (!element.disabled && element.name) {
|
| 3474 | + var value = element.getValue();
|
| 3475 | + if (value != undefined) {
|
| 3476 | + var pair = { };
|
| 3477 | + pair[element.name] = value;
|
| 3478 | + return Object.toQueryString(pair);
|
| 3479 | + }
|
| 3480 | + }
|
| 3481 | + return '';
|
| 3482 | + },
|
| 3483 | +
|
| 3484 | + getValue: function(element) {
|
| 3485 | + element = $(element);
|
| 3486 | + var method = element.tagName.toLowerCase();
|
| 3487 | + return Form.Element.Serializers[method](element);
|
| 3488 | + },
|
| 3489 | +
|
| 3490 | + setValue: function(element, value) {
|
| 3491 | + element = $(element);
|
| 3492 | + var method = element.tagName.toLowerCase();
|
| 3493 | + Form.Element.Serializers[method](element, value);
|
| 3494 | + return element;
|
| 3495 | + },
|
| 3496 | +
|
| 3497 | + clear: function(element) {
|
| 3498 | + $(element).value = '';
|
| 3499 | + return element;
|
| 3500 | + },
|
| 3501 | +
|
| 3502 | + present: function(element) {
|
| 3503 | + return $(element).value != '';
|
| 3504 | + },
|
| 3505 | +
|
| 3506 | + activate: function(element) {
|
| 3507 | + element = $(element);
|
| 3508 | + try {
|
| 3509 | + element.focus();
|
| 3510 | + if (element.select && (element.tagName.toLowerCase() != 'input' ||
|
| 3511 | + !['button', 'reset', 'submit'].include(element.type)))
|
| 3512 | + element.select();
|
| 3513 | + } catch (e) { }
|
| 3514 | + return element;
|
| 3515 | + },
|
| 3516 | +
|
| 3517 | + disable: function(element) {
|
| 3518 | + element = $(element);
|
| 3519 | + element.blur();
|
| 3520 | + element.disabled = true;
|
| 3521 | + return element;
|
| 3522 | + },
|
| 3523 | +
|
| 3524 | + enable: function(element) {
|
| 3525 | + element = $(element);
|
| 3526 | + element.disabled = false;
|
| 3527 | + return element;
|
| 3528 | + }
|
| 3529 | +};
|
| 3530 | +
|
| 3531 | +/*--------------------------------------------------------------------------*/
|
| 3532 | +
|
| 3533 | +var Field = Form.Element;
|
| 3534 | +var $F = Form.Element.Methods.getValue;
|
| 3535 | +
|
| 3536 | +/*--------------------------------------------------------------------------*/
|
| 3537 | +
|
| 3538 | +Form.Element.Serializers = {
|
| 3539 | + input: function(element, value) {
|
| 3540 | + switch (element.type.toLowerCase()) {
|
| 3541 | + case 'checkbox':
|
| 3542 | + case 'radio':
|
| 3543 | + return Form.Element.Serializers.inputSelector(element, value);
|
| 3544 | + default:
|
| 3545 | + return Form.Element.Serializers.textarea(element, value);
|
| 3546 | + }
|
| 3547 | + },
|
| 3548 | +
|
| 3549 | + inputSelector: function(element, value) {
|
| 3550 | + if (value === undefined) return element.checked ? element.value : null;
|
| 3551 | + else element.checked = !!value;
|
| 3552 | + },
|
| 3553 | +
|
| 3554 | + textarea: function(element, value) {
|
| 3555 | + if (value === undefined) return element.value;
|
| 3556 | + else element.value = value;
|
| 3557 | + },
|
| 3558 | +
|
| 3559 | + select: function(element, index) {
|
| 3560 | + if (index === undefined)
|
| 3561 | + return this[element.type == 'select-one' ?
|
| 3562 | + 'selectOne' : 'selectMany'](element);
|
| 3563 | + else {
|
| 3564 | + var opt, value, single = !Object.isArray(index);
|
| 3565 | + for (var i = 0, length = element.length; i < length; i++) {
|
| 3566 | + opt = element.options[i];
|
| 3567 | + value = this.optionValue(opt);
|
| 3568 | + if (single) {
|
| 3569 | + if (value == index) {
|
| 3570 | + opt.selected = true;
|
| 3571 | + return;
|
| 3572 | + }
|
| 3573 | + }
|
| 3574 | + else opt.selected = index.include(value);
|
| 3575 | + }
|
| 3576 | + }
|
| 3577 | + },
|
| 3578 | +
|
| 3579 | + selectOne: function(element) {
|
| 3580 | + var index = element.selectedIndex;
|
| 3581 | + return index >= 0 ? this.optionValue(element.options[index]) : null;
|
| 3582 | + },
|
| 3583 | +
|
| 3584 | + selectMany: function(element) {
|
| 3585 | + var values, length = element.length;
|
| 3586 | + if (!length) return null;
|
| 3587 | +
|
| 3588 | + for (var i = 0, values = []; i < length; i++) {
|
| 3589 | + var opt = element.options[i];
|
| 3590 | + if (opt.selected) values.push(this.optionValue(opt));
|
| 3591 | + }
|
| 3592 | + return values;
|
| 3593 | + },
|
| 3594 | +
|
| 3595 | + optionValue: function(opt) {
|
| 3596 | + // extend element because hasAttribute may not be native
|
| 3597 | + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
|
| 3598 | + }
|
| 3599 | +};
|
| 3600 | +
|
| 3601 | +/*--------------------------------------------------------------------------*/
|
| 3602 | +
|
| 3603 | +Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
|
| 3604 | + initialize: function($super, element, frequency, callback) {
|
| 3605 | + $super(callback, frequency);
|
| 3606 | + this.element = $(element);
|
| 3607 | + this.lastValue = this.getValue();
|
| 3608 | + },
|
| 3609 | +
|
| 3610 | + execute: function() {
|
| 3611 | + var value = this.getValue();
|
| 3612 | + if (Object.isString(this.lastValue) && Object.isString(value) ?
|
| 3613 | + this.lastValue != value : String(this.lastValue) != String(value)) {
|
| 3614 | + this.callback(this.element, value);
|
| 3615 | + this.lastValue = value;
|
| 3616 | + }
|
| 3617 | + }
|
| 3618 | +});
|
| 3619 | +
|
| 3620 | +Form.Element.Observer = Class.create(Abstract.TimedObserver, {
|
| 3621 | + getValue: function() {
|
| 3622 | + return Form.Element.getValue(this.element);
|
| 3623 | + }
|
| 3624 | +});
|
| 3625 | +
|
| 3626 | +Form.Observer = Class.create(Abstract.TimedObserver, {
|
| 3627 | + getValue: function() {
|
| 3628 | + return Form.serialize(this.element);
|
| 3629 | + }
|
| 3630 | +});
|
| 3631 | +
|
| 3632 | +/*--------------------------------------------------------------------------*/
|
| 3633 | +
|
| 3634 | +Abstract.EventObserver = Class.create({
|
| 3635 | + initialize: function(element, callback) {
|
| 3636 | + this.element = $(element);
|
| 3637 | + this.callback = callback;
|
| 3638 | +
|
| 3639 | + this.lastValue = this.getValue();
|
| 3640 | + if (this.element.tagName.toLowerCase() == 'form')
|
| 3641 | + this.registerFormCallbacks();
|
| 3642 | + else
|
| 3643 | + this.registerCallback(this.element);
|
| 3644 | + },
|
| 3645 | +
|
| 3646 | + onElementEvent: function() {
|
| 3647 | + var value = this.getValue();
|
| 3648 | + if (this.lastValue != value) {
|
| 3649 | + this.callback(this.element, value);
|
| 3650 | + this.lastValue = value;
|
| 3651 | + }
|
| 3652 | + },
|
| 3653 | +
|
| 3654 | + registerFormCallbacks: function() {
|
| 3655 | + Form.getElements(this.element).each(this.registerCallback, this);
|
| 3656 | + },
|
| 3657 | +
|
| 3658 | + registerCallback: function(element) {
|
| 3659 | + if (element.type) {
|
| 3660 | + switch (element.type.toLowerCase()) {
|
| 3661 | + case 'checkbox':
|
| 3662 | + case 'radio':
|
| 3663 | + Event.observe(element, 'click', this.onElementEvent.bind(this));
|
| 3664 | + break;
|
| 3665 | + default:
|
| 3666 | + Event.observe(element, 'change', this.onElementEvent.bind(this));
|
| 3667 | + break;
|
| 3668 | + }
|
| 3669 | + }
|
| 3670 | + }
|
| 3671 | +});
|
| 3672 | +
|
| 3673 | +Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
|
| 3674 | + getValue: function() {
|
| 3675 | + return Form.Element.getValue(this.element);
|
| 3676 | + }
|
| 3677 | +});
|
| 3678 | +
|
| 3679 | +Form.EventObserver = Class.create(Abstract.EventObserver, {
|
| 3680 | + getValue: function() {
|
| 3681 | + return Form.serialize(this.element);
|
| 3682 | + }
|
| 3683 | +});
|
| 3684 | +if (!window.Event) var Event = { };
|
| 3685 | +
|
| 3686 | +Object.extend(Event, {
|
| 3687 | + KEY_BACKSPACE: 8,
|
| 3688 | + KEY_TAB: 9,
|
| 3689 | + KEY_RETURN: 13,
|
| 3690 | + KEY_ESC: 27,
|
| 3691 | + KEY_LEFT: 37,
|
| 3692 | + KEY_UP: 38,
|
| 3693 | + KEY_RIGHT: 39,
|
| 3694 | + KEY_DOWN: 40,
|
| 3695 | + KEY_DELETE: 46,
|
| 3696 | + KEY_HOME: 36,
|
| 3697 | + KEY_END: 35,
|
| 3698 | + KEY_PAGEUP: 33,
|
| 3699 | + KEY_PAGEDOWN: 34,
|
| 3700 | + KEY_INSERT: 45,
|
| 3701 | +
|
| 3702 | + cache: { },
|
| 3703 | +
|
| 3704 | + relatedTarget: function(event) {
|
| 3705 | + var element;
|
| 3706 | + switch(event.type) {
|
| 3707 | + case 'mouseover': element = event.fromElement; break;
|
| 3708 | + case 'mouseout': element = event.toElement; break;
|
| 3709 | + default: return null;
|
| 3710 | + }
|
| 3711 | + return Element.extend(element);
|
| 3712 | + }
|
| 3713 | +});
|
| 3714 | +
|
| 3715 | +Event.Methods = (function() {
|
| 3716 | + var isButton;
|
| 3717 | +
|
| 3718 | + if (Prototype.Browser.IE) {
|
| 3719 | + var buttonMap = { 0: 1, 1: 4, 2: 2 };
|
| 3720 | + isButton = function(event, code) {
|
| 3721 | + return event.button == buttonMap[code];
|
| 3722 | + };
|
| 3723 | +
|
| 3724 | + } else if (Prototype.Browser.WebKit) {
|
| 3725 | + isButton = function(event, code) {
|
| 3726 | + switch (code) {
|
| 3727 | + case 0: return event.which == 1 && !event.metaKey;
|
| 3728 | + case 1: return event.which == 1 && event.metaKey;
|
| 3729 | + default: return false;
|
| 3730 | + }
|
| 3731 | + };
|
| 3732 | +
|
| 3733 | + } else {
|
| 3734 | + isButton = function(event, code) {
|
| 3735 | + return event.which ? (event.which === code + 1) : (event.button === code);
|
| 3736 | + };
|
| 3737 | + }
|
| 3738 | +
|
| 3739 | + return {
|
| 3740 | + isLeftClick: function(event) { return isButton(event, 0) },
|
| 3741 | + isMiddleClick: function(event) { return isButton(event, 1) },
|
| 3742 | + isRightClick: function(event) { return isButton(event, 2) },
|
| 3743 | +
|
| 3744 | + element: function(event) {
|
| 3745 | + var node = Event.extend(event).target;
|
| 3746 | + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
|
| 3747 | + },
|
| 3748 | +
|
| 3749 | + findElement: function(event, expression) {
|
| 3750 | + var element = Event.element(event);
|
| 3751 | + return element.match(expression) ? element : element.up(expression);
|
| 3752 | + },
|
| 3753 | +
|
| 3754 | + pointer: function(event) {
|
| 3755 | + return {
|
| 3756 | + x: event.pageX || (event.clientX +
|
| 3757 | + (document.documentElement.scrollLeft || document.body.scrollLeft)),
|
| 3758 | + y: event.pageY || (event.clientY +
|
| 3759 | + (document.documentElement.scrollTop || document.body.scrollTop))
|
| 3760 | + };
|
| 3761 | + },
|
| 3762 | +
|
| 3763 | + pointerX: function(event) { return Event.pointer(event).x },
|
| 3764 | + pointerY: function(event) { return Event.pointer(event).y },
|
| 3765 | +
|
| 3766 | + stop: function(event) {
|
| 3767 | + Event.extend(event);
|
| 3768 | + event.preventDefault();
|
| 3769 | + event.stopPropagation();
|
| 3770 | + event.stopped = true;
|
| 3771 | + }
|
| 3772 | + };
|
| 3773 | +})();
|
| 3774 | +
|
| 3775 | +Event.extend = (function() {
|
| 3776 | + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
|
| 3777 | + m[name] = Event.Methods[name].methodize();
|
| 3778 | + return m;
|
| 3779 | + });
|
| 3780 | +
|
| 3781 | + if (Prototype.Browser.IE) {
|
| 3782 | + Object.extend(methods, {
|
| 3783 | + stopPropagation: function() { this.cancelBubble = true },
|
| 3784 | + preventDefault: function() { this.returnValue = false },
|
| 3785 | + inspect: function() { return "[object Event]" }
|
| 3786 | + });
|
| 3787 | +
|
| 3788 | + return function(event) {
|
| 3789 | + if (!event) return false;
|
| 3790 | + if (event._extendedByPrototype) return event;
|
| 3791 | +
|
| 3792 | + event._extendedByPrototype = Prototype.emptyFunction;
|
| 3793 | + var pointer = Event.pointer(event);
|
| 3794 | + Object.extend(event, {
|
| 3795 | + target: event.srcElement,
|
| 3796 | + relatedTarget: Event.relatedTarget(event),
|
| 3797 | + pageX: pointer.x,
|
| 3798 | + pageY: pointer.y
|
| 3799 | + });
|
| 3800 | + return Object.extend(event, methods);
|
| 3801 | + };
|
| 3802 | +
|
| 3803 | + } else {
|
| 3804 | + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
|
| 3805 | + Object.extend(Event.prototype, methods);
|
| 3806 | + return Prototype.K;
|
| 3807 | + }
|
| 3808 | +})();
|
| 3809 | +
|
| 3810 | +Object.extend(Event, (function() {
|
| 3811 | + var cache = Event.cache;
|
| 3812 | +
|
| 3813 | + function getEventID(element) {
|
| 3814 | + if (element._eventID) return element._eventID;
|
| 3815 | + arguments.callee.id = arguments.callee.id || 1;
|
| 3816 | + return element._eventID = ++arguments.callee.id;
|
| 3817 | + }
|
| 3818 | +
|
| 3819 | + function getDOMEventName(eventName) {
|
| 3820 | + if (eventName && eventName.include(':')) return "dataavailable";
|
| 3821 | + return eventName;
|
| 3822 | + }
|
| 3823 | +
|
| 3824 | + function getCacheForID(id) {
|
| 3825 | + return cache[id] = cache[id] || { };
|
| 3826 | + }
|
| 3827 | +
|
| 3828 | + function getWrappersForEventName(id, eventName) {
|
| 3829 | + var c = getCacheForID(id);
|
| 3830 | + return c[eventName] = c[eventName] || [];
|
| 3831 | + }
|
| 3832 | +
|
| 3833 | + function createWrapper(element, eventName, handler) {
|
| 3834 | + var id = getEventID(element);
|
| 3835 | + var c = getWrappersForEventName(id, eventName);
|
| 3836 | + if (c.pluck("handler").include(handler)) return false;
|
| 3837 | +
|
| 3838 | + var wrapper = function(event) {
|
| 3839 | + if (!Event || !Event.extend ||
|
| 3840 | + (event.eventName && event.eventName != eventName))
|
| 3841 | + return false;
|
| 3842 | +
|
| 3843 | + Event.extend(event);
|
| 3844 | + handler.call(element, event)
|
| 3845 | + };
|
| 3846 | +
|
| 3847 | + wrapper.handler = handler;
|
| 3848 | + c.push(wrapper);
|
| 3849 | + return wrapper;
|
| 3850 | + }
|
| 3851 | +
|
| 3852 | + function findWrapper(id, eventName, handler) {
|
| 3853 | + var c = getWrappersForEventName(id, eventName);
|
| 3854 | + return c.find(function(wrapper) { return wrapper.handler == handler });
|
| 3855 | + }
|
| 3856 | +
|
| 3857 | + function destroyWrapper(id, eventName, handler) {
|
| 3858 | + var c = getCacheForID(id);
|
| 3859 | + if (!c[eventName]) return false;
|
| 3860 | + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
|
| 3861 | + }
|
| 3862 | +
|
| 3863 | + function destroyCache() {
|
| 3864 | + for (var id in cache)
|
| 3865 | + for (var eventName in cache[id])
|
| 3866 | + cache[id][eventName] = null;
|
| 3867 | + }
|
| 3868 | +
|
| 3869 | + if (window.attachEvent) {
|
| 3870 | + window.attachEvent("onunload", destroyCache);
|
| 3871 | + }
|
| 3872 | +
|
| 3873 | + return {
|
| 3874 | + observe: function(element, eventName, handler) {
|
| 3875 | + element = $(element);
|
| 3876 | + var name = getDOMEventName(eventName);
|
| 3877 | +
|
| 3878 | + var wrapper = createWrapper(element, eventName, handler);
|
| 3879 | + if (!wrapper) return element;
|
| 3880 | +
|
| 3881 | + if (element.addEventListener) {
|
| 3882 | + element.addEventListener(name, wrapper, false);
|
| 3883 | + } else {
|
| 3884 | + element.attachEvent("on" + name, wrapper);
|
| 3885 | + }
|
| 3886 | +
|
| 3887 | + return element;
|
| 3888 | + },
|
| 3889 | +
|
| 3890 | + stopObserving: function(element, eventName, handler) {
|
| 3891 | + element = $(element);
|
| 3892 | + var id = getEventID(element), name = getDOMEventName(eventName);
|
| 3893 | +
|
| 3894 | + if (!handler && eventName) {
|
| 3895 | + getWrappersForEventName(id, eventName).each(function(wrapper) {
|
| 3896 | + element.stopObserving(eventName, wrapper.handler);
|
| 3897 | + });
|
| 3898 | + return element;
|
| 3899 | +
|
| 3900 | + } else if (!eventName) {
|
| 3901 | + Object.keys(getCacheForID(id)).each(function(eventName) {
|
| 3902 | + element.stopObserving(eventName);
|
| 3903 | + });
|
| 3904 | + return element;
|
| 3905 | + }
|
| 3906 | +
|
| 3907 | + var wrapper = findWrapper(id, eventName, handler);
|
| 3908 | + if (!wrapper) return element;
|
| 3909 | +
|
| 3910 | + if (element.removeEventListener) {
|
| 3911 | + element.removeEventListener(name, wrapper, false);
|
| 3912 | + } else {
|
| 3913 | + element.detachEvent("on" + name, wrapper);
|
| 3914 | + }
|
| 3915 | +
|
| 3916 | + destroyWrapper(id, eventName, handler);
|
| 3917 | +
|
| 3918 | + return element;
|
| 3919 | + },
|
| 3920 | +
|
| 3921 | + fire: function(element, eventName, memo) {
|
| 3922 | + element = $(element);
|
| 3923 | + if (element == document && document.createEvent && !element.dispatchEvent)
|
| 3924 | + element = document.documentElement;
|
| 3925 | +
|
| 3926 | + if (document.createEvent) {
|
| 3927 | + var event = document.createEvent("HTMLEvents");
|
| 3928 | + event.initEvent("dataavailable", true, true);
|
| 3929 | + } else {
|
| 3930 | + var event = document.createEventObject();
|
| 3931 | + event.eventType = "ondataavailable";
|
| 3932 | + }
|
| 3933 | +
|
| 3934 | + event.eventName = eventName;
|
| 3935 | + event.memo = memo || { };
|
| 3936 | +
|
| 3937 | + if (document.createEvent) {
|
| 3938 | + element.dispatchEvent(event);
|
| 3939 | + } else {
|
| 3940 | + element.fireEvent(event.eventType, event);
|
| 3941 | + }
|
| 3942 | +
|
| 3943 | + return event;
|
| 3944 | + }
|
| 3945 | + };
|
| 3946 | +})());
|
| 3947 | +
|
| 3948 | +Object.extend(Event, Event.Methods);
|
| 3949 | +
|
| 3950 | +Element.addMethods({
|
| 3951 | + fire: Event.fire,
|
| 3952 | + observe: Event.observe,
|
| 3953 | + stopObserving: Event.stopObserving
|
| 3954 | +});
|
| 3955 | +
|
| 3956 | +Object.extend(document, {
|
| 3957 | + fire: Element.Methods.fire.methodize(),
|
| 3958 | + observe: Element.Methods.observe.methodize(),
|
| 3959 | + stopObserving: Element.Methods.stopObserving.methodize()
|
| 3960 | +});
|
| 3961 | +
|
| 3962 | +(function() {
|
| 3963 | + /* Support for the DOMContentLoaded event is based on work by Dan Webb,
|
| 3964 | + Matthias Miller, Dean Edwards and John Resig. */
|
| 3965 | +
|
| 3966 | + var timer, fired = false;
|
| 3967 | +
|
| 3968 | + function fireContentLoadedEvent() {
|
| 3969 | + if (fired) return;
|
| 3970 | + if (timer) window.clearInterval(timer);
|
| 3971 | + document.fire("dom:loaded");
|
| 3972 | + fired = true;
|
| 3973 | + }
|
| 3974 | +
|
| 3975 | + if (document.addEventListener) {
|
| 3976 | + if (Prototype.Browser.WebKit) {
|
| 3977 | + timer = window.setInterval(function() {
|
| 3978 | + if (/loaded|complete/.test(document.readyState))
|
| 3979 | + fireContentLoadedEvent();
|
| 3980 | + }, 0);
|
| 3981 | +
|
| 3982 | + Event.observe(window, "load", fireContentLoadedEvent);
|
| 3983 | +
|
| 3984 | + } else {
|
| 3985 | + document.addEventListener("DOMContentLoaded",
|
| 3986 | + fireContentLoadedEvent, false);
|
| 3987 | + }
|
| 3988 | +
|
| 3989 | + } else {
|
| 3990 | + document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
|
| 3991 | + $("__onDOMContentLoaded").onreadystatechange = function() {
|
| 3992 | + if (this.readyState == "complete") {
|
| 3993 | + this.onreadystatechange = null;
|
| 3994 | + fireContentLoadedEvent();
|
| 3995 | + }
|
| 3996 | + };
|
| 3997 | + }
|
| 3998 | +})();
|
| 3999 | +/*------------------------------- DEPRECATED -------------------------------*/
|
| 4000 | +
|
| 4001 | +Hash.toQueryString = Object.toQueryString;
|
| 4002 | +
|
| 4003 | +var Toggle = { display: Element.toggle };
|
| 4004 | +
|
| 4005 | +Element.Methods.childOf = Element.Methods.descendantOf;
|
| 4006 | +
|
| 4007 | +var Insertion = {
|
| 4008 | + Before: function(element, content) {
|
| 4009 | + return Element.insert(element, {before:content});
|
| 4010 | + },
|
| 4011 | +
|
| 4012 | + Top: function(element, content) {
|
| 4013 | + return Element.insert(element, {top:content});
|
| 4014 | + },
|
| 4015 | +
|
| 4016 | + Bottom: function(element, content) {
|
| 4017 | + return Element.insert(element, {bottom:content});
|
| 4018 | + },
|
| 4019 | +
|
| 4020 | + After: function(element, content) {
|
| 4021 | + return Element.insert(element, {after:content});
|
| 4022 | + }
|
| 4023 | +};
|
| 4024 | +
|
| 4025 | +var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
|
| 4026 | +
|
| 4027 | +// This should be moved to script.aculo.us; notice the deprecated methods
|
| 4028 | +// further below, that map to the newer Element methods.
|
| 4029 | +var Position = {
|
| 4030 | + // set to true if needed, warning: firefox performance problems
|
| 4031 | + // NOT neeeded for page scrolling, only if draggable contained in
|
| 4032 | + // scrollable elements
|
| 4033 | + includeScrollOffsets: false,
|
| 4034 | +
|
| 4035 | + // must be called before calling withinIncludingScrolloffset, every time the
|
| 4036 | + // page is scrolled
|
| 4037 | + prepare: function() {
|
| 4038 | + this.deltaX = window.pageXOffset
|
| 4039 | + || document.documentElement.scrollLeft
|
| 4040 | + || document.body.scrollLeft
|
| 4041 | + || 0;
|
| 4042 | + this.deltaY = window.pageYOffset
|
| 4043 | + || document.documentElement.scrollTop
|
| 4044 | + || document.body.scrollTop
|
| 4045 | + || 0;
|
| 4046 | + },
|
| 4047 | +
|
| 4048 | + // caches x/y coordinate pair to use with overlap
|
| 4049 | + within: function(element, x, y) {
|
| 4050 | + if (this.includeScrollOffsets)
|
| 4051 | + return this.withinIncludingScrolloffsets(element, x, y);
|
| 4052 | + this.xcomp = x;
|
| 4053 | + this.ycomp = y;
|
| 4054 | + this.offset = Element.cumulativeOffset(element);
|
| 4055 | +
|
| 4056 | + return (y >= this.offset[1] &&
|
| 4057 | + y < this.offset[1] + element.offsetHeight &&
|
| 4058 | + x >= this.offset[0] &&
|
| 4059 | + x < this.offset[0] + element.offsetWidth);
|
| 4060 | + },
|
| 4061 | +
|
| 4062 | + withinIncludingScrolloffsets: function(element, x, y) {
|
| 4063 | + var offsetcache = Element.cumulativeScrollOffset(element);
|
| 4064 | +
|
| 4065 | + this.xcomp = x + offsetcache[0] - this.deltaX;
|
| 4066 | + this.ycomp = y + offsetcache[1] - this.deltaY;
|
| 4067 | + this.offset = Element.cumulativeOffset(element);
|
| 4068 | +
|
| 4069 | + return (this.ycomp >= this.offset[1] &&
|
| 4070 | + this.ycomp < this.offset[1] + element.offsetHeight &&
|
| 4071 | + this.xcomp >= this.offset[0] &&
|
| 4072 | + this.xcomp < this.offset[0] + element.offsetWidth);
|
| 4073 | + },
|
| 4074 | +
|
| 4075 | + // within must be called directly before
|
| 4076 | + overlap: function(mode, element) {
|
| 4077 | + if (!mode) return 0;
|
| 4078 | + if (mode == 'vertical')
|
| 4079 | + return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
|
| 4080 | + element.offsetHeight;
|
| 4081 | + if (mode == 'horizontal')
|
| 4082 | + return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
|
| 4083 | + element.offsetWidth;
|
| 4084 | + },
|
| 4085 | +
|
| 4086 | + // Deprecation layer -- use newer Element methods now (1.5.2).
|
| 4087 | +
|
| 4088 | + cumulativeOffset: Element.Methods.cumulativeOffset,
|
| 4089 | +
|
| 4090 | + positionedOffset: Element.Methods.positionedOffset,
|
| 4091 | +
|
| 4092 | + absolutize: function(element) {
|
| 4093 | + Position.prepare();
|
| 4094 | + return Element.absolutize(element);
|
| 4095 | + },
|
| 4096 | +
|
| 4097 | + relativize: function(element) {
|
| 4098 | + Position.prepare();
|
| 4099 | + return Element.relativize(element);
|
| 4100 | + },
|
| 4101 | +
|
| 4102 | + realOffset: Element.Methods.cumulativeScrollOffset,
|
| 4103 | +
|
| 4104 | + offsetParent: Element.Methods.getOffsetParent,
|
| 4105 | +
|
| 4106 | + page: Element.Methods.viewportOffset,
|
| 4107 | +
|
| 4108 | + clone: function(source, target, options) {
|
| 4109 | + options = options || { };
|
| 4110 | + return Element.clonePosition(target, source, options);
|
| 4111 | + }
|
| 4112 | +};
|
| 4113 | +
|
| 4114 | +/*--------------------------------------------------------------------------*/
|
| 4115 | +
|
| 4116 | +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
|
| 4117 | + function iter(name) {
|
| 4118 | + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
|
| 4119 | + }
|
| 4120 | +
|
| 4121 | + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
|
| 4122 | + function(element, className) {
|
| 4123 | + className = className.toString().strip();
|
| 4124 | + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
|
| 4125 | + return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
|
| 4126 | + } : function(element, className) {
|
| 4127 | + className = className.toString().strip();
|
| 4128 | + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
|
| 4129 | + if (!classNames && !className) return elements;
|
| 4130 | +
|
| 4131 | + var nodes = $(element).getElementsByTagName('*');
|
| 4132 | + className = ' ' + className + ' ';
|
| 4133 | +
|
| 4134 | + for (var i = 0, child, cn; child = nodes[i]; i++) {
|
| 4135 | + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
|
| 4136 | + (classNames && classNames.all(function(name) {
|
| 4137 | + return !name.toString().blank() && cn.include(' ' + name + ' ');
|
| 4138 | + }))))
|
| 4139 | + elements.push(Element.extend(child));
|
| 4140 | + }
|
| 4141 | + return elements;
|
| 4142 | + };
|
| 4143 | +
|
| 4144 | + return function(className, parentElement) {
|
| 4145 | + return $(parentElement || document.body).getElementsByClassName(className);
|
| 4146 | + };
|
| 4147 | +}(Element.Methods);
|
| 4148 | +
|
| 4149 | +/*--------------------------------------------------------------------------*/
|
| 4150 | +
|
| 4151 | +Element.ClassNames = Class.create();
|
| 4152 | +Element.ClassNames.prototype = {
|
| 4153 | + initialize: function(element) {
|
| 4154 | + this.element = $(element);
|
| 4155 | + },
|
| 4156 | +
|
| 4157 | + _each: function(iterator) {
|
| 4158 | + this.element.className.split(/\s+/).select(function(name) {
|
| 4159 | + return name.length > 0;
|
| 4160 | + })._each(iterator);
|
| 4161 | + },
|
| 4162 | +
|
| 4163 | + set: function(className) {
|
| 4164 | + this.element.className = className;
|
| 4165 | + },
|
| 4166 | +
|
| 4167 | + add: function(classNameToAdd) {
|
| 4168 | + if (this.include(classNameToAdd)) return;
|
| 4169 | + this.set($A(this).concat(classNameToAdd).join(' '));
|
| 4170 | + },
|
| 4171 | +
|
| 4172 | + remove: function(classNameToRemove) {
|
| 4173 | + if (!this.include(classNameToRemove)) return;
|
| 4174 | + this.set($A(this).without(classNameToRemove).join(' '));
|
| 4175 | + },
|
| 4176 | +
|
| 4177 | + toString: function() {
|
| 4178 | + return $A(this).join(' ');
|
| 4179 | + }
|
| 4180 | +};
|
| 4181 | +
|
| 4182 | +Object.extend(Element.ClassNames.prototype, Enumerable);
|
| 4183 | +
|
| 4184 | +/*--------------------------------------------------------------------------*/
|
| 4185 | +
|
| 4186 | +Element.addMethods(); |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/scripts/Language/SMW_NMLanguage.js |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +/**
|
| 3 | +* SMW_NMLanguage.js
|
| 4 | +*
|
| 5 | +* A class that reads language strings from the server by an ajax call.
|
| 6 | +*
|
| 7 | +* @author dch
|
| 8 | +*
|
| 9 | +*/
|
| 10 | +
|
| 11 | +var NMLanguage = Class.create();
|
| 12 | +
|
| 13 | +/**
|
| 14 | + * This class provides language dependent strings for an identifier.
|
| 15 | + *
|
| 16 | + */
|
| 17 | +NMLanguage.prototype = {
|
| 18 | +
|
| 19 | + /**
|
| 20 | + * @public
|
| 21 | + *
|
| 22 | + * Constructor.
|
| 23 | + */
|
| 24 | + initialize: function() {
|
| 25 | + },
|
| 26 | +
|
| 27 | + /*
|
| 28 | + * @public
|
| 29 | + *
|
| 30 | + * Returns a language dependent message for an ID, or the ID, if there is
|
| 31 | + * no message for it.
|
| 32 | + *
|
| 33 | + * @param string id
|
| 34 | + * ID of the message to be retrieved.
|
| 35 | + * @return string
|
| 36 | + * The language dependent message for the given ID.
|
| 37 | + */
|
| 38 | + getMessage: function(id, type) {
|
| 39 | + switch (type) {
|
| 40 | + case "user":
|
| 41 | + var msg = wgNMUserLanguageStrings[id];
|
| 42 | + if (!msg) {
|
| 43 | + msg = id;
|
| 44 | + }
|
| 45 | + break;
|
| 46 | +
|
| 47 | + case "cont":
|
| 48 | + var msg = wgNMContLanguageStrings[id];
|
| 49 | + if (!msg) {
|
| 50 | + msg = id;
|
| 51 | + }
|
| 52 | + break;
|
| 53 | + default:
|
| 54 | + var msg = wgNMUserLanguageStrings[id];
|
| 55 | + if (!msg) {
|
| 56 | + var msg = wgNMContLanguageStrings[id];
|
| 57 | + if (!msg) {
|
| 58 | + msg = id;
|
| 59 | + }
|
| 60 | + }
|
| 61 | + }
|
| 62 | +
|
| 63 | + // Replace variables
|
| 64 | + msg = msg.replace(/\$n/g,wgCanonicalNamespace);
|
| 65 | + msg = msg.replace(/\$p/g,wgPageName);
|
| 66 | + msg = msg.replace(/\$t/g,wgTitle);
|
| 67 | + msg = msg.replace(/\$u/g,wgUserName);
|
| 68 | + msg = msg.replace(/\$s/g,wgServer);
|
| 69 | + return msg;
|
| 70 | + }
|
| 71 | +
|
| 72 | +}
|
| 73 | +
|
| 74 | +// Singleton of this class
|
| 75 | +
|
| 76 | +var nmLanguage = new NMLanguage(); |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/scripts/Language/SMW_NMLanguageUserEn.js |
— | — | @@ -0,0 +1,4 @@ |
| 2 | +var wgNMUserLanguageStrings = {
|
| 3 | + 'NM_EMPTY_NOTIFYNAME' : 'Your Notify-name is empty.',
|
| 4 | + 'NM_EMPTY_QUERY' : 'Your query is empty.',
|
| 5 | +};
|
Index: trunk/extensions/SemanticNotifyMe/scripts/Language/SMW_NMLanguageEn.js |
— | — | @@ -0,0 +1,3 @@ |
| 2 | +var wgNMContLanguageStrings = {
|
| 3 | + 'NOTIFYME_NS' : 'NotifyMe:' //namespace identifier with colon
|
| 4 | +} |
\ No newline at end of file |
Index: trunk/extensions/SemanticNotifyMe/scripts/NotifyMe/nm_tooltip.js |
— | — | @@ -0,0 +1,1149 @@ |
| 2 | +/* This notice must be untouched at all times.
|
| 3 | +
|
| 4 | +wz_tooltip.js v. 4.12
|
| 5 | +
|
| 6 | +The latest version is available at
|
| 7 | +http://www.walterzorn.com
|
| 8 | +or http://www.devira.com
|
| 9 | +or http://www.walterzorn.de
|
| 10 | +
|
| 11 | +Copyright (c) 2002-2007 Walter Zorn. All rights reserved.
|
| 12 | +Created 1.12.2002 by Walter Zorn (Web: http://www.walterzorn.com )
|
| 13 | +Last modified: 13.7.2007
|
| 14 | +
|
| 15 | +Easy-to-use cross-browser tooltips.
|
| 16 | +Just include the script at the beginning of the <body> section, and invoke
|
| 17 | +Tip('Tooltip text') from within the desired HTML onmouseover eventhandlers.
|
| 18 | +No container DIV, no onmouseouts required.
|
| 19 | +By default, width of tooltips is automatically adapted to content.
|
| 20 | +Is even capable of dynamically converting arbitrary HTML elements to tooltips
|
| 21 | +by calling TagToTip('ID_of_HTML_element_to_be_converted') instead of Tip(),
|
| 22 | +which means you can put important, search-engine-relevant stuff into tooltips.
|
| 23 | +Appearance of tooltips can be individually configured
|
| 24 | +via commands passed to Tip() or TagToTip().
|
| 25 | +
|
| 26 | +Tab Width: 4
|
| 27 | +LICENSE: LGPL
|
| 28 | +
|
| 29 | +This library is free software; you can redistribute it and/or
|
| 30 | +modify it under the terms of the GNU Lesser General Public
|
| 31 | +License (LGPL) as published by the Free Software Foundation; either
|
| 32 | +version 2.1 of the License, or (at your option) any later version.
|
| 33 | +
|
| 34 | +This library is distributed in the hope that it will be useful,
|
| 35 | +but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 36 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
| 37 | +
|
| 38 | +For more details on the GNU Lesser General Public License,
|
| 39 | +see http://www.gnu.org/copyleft/lesser.html
|
| 40 | +*/
|
| 41 | +
|
| 42 | +var config = new Object();
|
| 43 | +
|
| 44 | +
|
| 45 | +//=================== GLOBAL TOOPTIP CONFIGURATION =========================//
|
| 46 | +var tt_Debug = false // false or true - recommended: false once you release your page to the public
|
| 47 | +var tt_Enabled = true // Allows to (temporarily) suppress tooltips, e.g. by providing the user with a button that sets this global variable to false
|
| 48 | +var TagsToTip = true // false or true - if true, the script is capable of converting HTML elements to tooltips
|
| 49 | +
|
| 50 | +// For each of the following config variables there exists a command, which is
|
| 51 | +// just the variablename in uppercase, to be passed to Tip() or TagToTip() to
|
| 52 | +// configure tooltips individually. Individual commands override global
|
| 53 | +// configuration. Order of commands is arbitrary.
|
| 54 | +// Example: onmouseover="Tip('Tooltip text', LEFT, true, BGCOLOR, '#FF9900', FADEIN, 400)"
|
| 55 | +
|
| 56 | +config. Above = false // false or true - tooltip above mousepointer?
|
| 57 | +config. BgColor = '#E4E7FF' // Background color
|
| 58 | +config. BgImg = '' // Path to background image, none if empty string ''
|
| 59 | +config. BorderColor = '#002299'
|
| 60 | +config. BorderStyle = 'solid' // Any permitted CSS value, but I recommend 'solid', 'dotted' or 'dashed'
|
| 61 | +config. BorderWidth = 1
|
| 62 | +config. CenterMouse = false // false or true - center the tip horizontally below (or above) the mousepointer
|
| 63 | +config. ClickClose = false // false or true - close tooltip if the user clicks somewhere
|
| 64 | +config. CloseBtn = false // false or true - closebutton in titlebar
|
| 65 | +config. CloseBtnColors = ['#990000', '#FFFFFF', '#DD3333', '#FFFFFF'] // [Background, text, hovered background, hovered text] - use empty strings '' to inherit title colors
|
| 66 | +config. CloseBtnText = ' X ' // Close button text (may also be an image tag)
|
| 67 | +config. CopyContent = true // When converting a HTML element to a tooltip, copy only the element's content, rather than converting the element by its own
|
| 68 | +config. Delay = 400 // Time span in ms until tooltip shows up
|
| 69 | +config. Duration = 0 // Time span in ms after which the tooltip disappears; 0 for infinite duration
|
| 70 | +config. FadeIn = 0 // Fade-in duration in ms, e.g. 400; 0 for no animation
|
| 71 | +config. FadeOut = 0
|
| 72 | +config. FadeInterval = 30 // Duration of each fade step in ms (recommended: 30) - shorter is smoother but causes more CPU-load
|
| 73 | +config. Fix = null // Fixated position - x- an y-oordinates in brackets, e.g. [210, 480], or null for no fixation
|
| 74 | +config. FollowMouse = true // false or true - tooltip follows the mouse
|
| 75 | +config. FontColor = '#000044'
|
| 76 | +config. FontFace = 'Verdana,Geneva,sans-serif'
|
| 77 | +config. FontSize = '8pt' // E.g. '9pt' or '12px' - unit is mandatory
|
| 78 | +config. FontWeight = 'normal' // 'normal' or 'bold';
|
| 79 | +config. Left = false // false or true - tooltip on the left of the mouse
|
| 80 | +config. OffsetX = 14 // Horizontal offset of left-top corner from mousepointer
|
| 81 | +config. OffsetY = 8 // Vertical offset
|
| 82 | +config. Opacity = 100 // Integer between 0 and 100 - opacity of tooltip in percent
|
| 83 | +config. Padding = 3 // Spacing between border and content
|
| 84 | +config. Shadow = false // false or true
|
| 85 | +config. ShadowColor = '#C0C0C0'
|
| 86 | +config. ShadowWidth = 5
|
| 87 | +config. Sticky = false // Do NOT hide tooltip on mouseout? false or true
|
| 88 | +config. TextAlign = 'left' // 'left', 'right' or 'justify'
|
| 89 | +config. Title = '' // Default title text applied to all tips (no default title: empty string '')
|
| 90 | +config. TitleAlign = 'left' // 'left' or 'right' - text alignment inside the title bar
|
| 91 | +config. TitleBgColor = '' // If empty string '', BorderColor will be used
|
| 92 | +config. TitleFontColor = '#ffffff' // Color of title text - if '', BgColor (of tooltip body) will be used
|
| 93 | +config. TitleFontFace = '' // If '' use FontFace (boldified)
|
| 94 | +config. TitleFontSize = '' // If '' use FontSize
|
| 95 | +config. Width = 200 // Tooltip width; 0 for automatic adaption to tooltip content
|
| 96 | +//======= END OF TOOLTIP CONFIG, DO NOT CHANGE ANYTHING BELOW ==============//
|
| 97 | +
|
| 98 | +
|
| 99 | +
|
| 100 | +
|
| 101 | +//====================== PUBLIC ============================================//
|
| 102 | +function Tip()
|
| 103 | +{
|
| 104 | + tt_Tip(arguments, null);
|
| 105 | +}
|
| 106 | +function TagToTip()
|
| 107 | +{
|
| 108 | + if(TagsToTip)
|
| 109 | + {
|
| 110 | + var t2t = tt_GetElt(arguments[0]);
|
| 111 | + if(t2t)
|
| 112 | + tt_Tip(arguments, t2t);
|
| 113 | + }
|
| 114 | +}
|
| 115 | +
|
| 116 | +//================== PUBLIC EXTENSION API ==================================//
|
| 117 | +// Extension eventhandlers currently supported:
|
| 118 | +// OnLoadConfig, OnCreateContentString, OnSubDivsCreated, OnShow, OnMoveBefore,
|
| 119 | +// OnMoveAfter, OnHideInit, OnHide, OnKill
|
| 120 | +
|
| 121 | +var tt_aElt = new Array(10), // Container DIV, outer title & body DIVs, inner title & body TDs, closebutton SPAN, shadow DIVs, and IFRAME to cover windowed elements in IE
|
| 122 | +tt_aV = new Array(), // Caches and enumerates config data for currently active tooltip
|
| 123 | +tt_sContent, // Inner tooltip text or HTML
|
| 124 | +tt_scrlX = 0, tt_scrlY = 0,
|
| 125 | +tt_musX, tt_musY,
|
| 126 | +tt_over,
|
| 127 | +tt_x, tt_y, tt_w, tt_h; // Position, width and height of currently displayed tooltip
|
| 128 | +
|
| 129 | +function tt_Extension()
|
| 130 | +{
|
| 131 | + tt_ExtCmdEnum();
|
| 132 | + tt_aExt[tt_aExt.length] = this;
|
| 133 | + return this;
|
| 134 | +}
|
| 135 | +function tt_SetTipPos(x, y)
|
| 136 | +{
|
| 137 | + var css = tt_aElt[0].style;
|
| 138 | +
|
| 139 | + tt_x = x;
|
| 140 | + tt_y = y;
|
| 141 | + css.left = x + "px";
|
| 142 | + css.top = y + "px";
|
| 143 | + if(tt_ie56)
|
| 144 | + {
|
| 145 | + var ifrm = tt_aElt[tt_aElt.length - 1];
|
| 146 | + if(ifrm)
|
| 147 | + {
|
| 148 | + ifrm.style.left = css.left;
|
| 149 | + ifrm.style.top = css.top;
|
| 150 | + }
|
| 151 | + }
|
| 152 | +}
|
| 153 | +function tt_Hide()
|
| 154 | +{
|
| 155 | + if(tt_db && tt_iState)
|
| 156 | + {
|
| 157 | + if(tt_iState & 0x2)
|
| 158 | + {
|
| 159 | + tt_aElt[0].style.visibility = "hidden";
|
| 160 | + tt_ExtCallFncs(0, "Hide");
|
| 161 | + }
|
| 162 | + tt_tShow.EndTimer();
|
| 163 | + tt_tHide.EndTimer();
|
| 164 | + tt_tDurt.EndTimer();
|
| 165 | + tt_tFade.EndTimer();
|
| 166 | + if(!tt_op && !tt_ie)
|
| 167 | + {
|
| 168 | + tt_tWaitMov.EndTimer();
|
| 169 | + tt_bWait = false;
|
| 170 | + }
|
| 171 | + if(tt_aV[CLICKCLOSE])
|
| 172 | + tt_RemEvtFnc(document, "mouseup", tt_HideInit);
|
| 173 | + tt_AddRemOutFnc(false);
|
| 174 | + tt_ExtCallFncs(0, "Kill");
|
| 175 | + // In case of a TagToTip tooltip, hide converted DOM node and
|
| 176 | + // re-insert it into document
|
| 177 | + if(tt_t2t && !tt_aV[COPYCONTENT])
|
| 178 | + {
|
| 179 | + tt_t2t.style.display = "none";
|
| 180 | + tt_MovDomNode(tt_t2t, tt_aElt[6], tt_t2tDad);
|
| 181 | + }
|
| 182 | + tt_iState = 0;
|
| 183 | + tt_over = null;
|
| 184 | + tt_ResetMainDiv();
|
| 185 | + if(tt_aElt[tt_aElt.length - 1])
|
| 186 | + tt_aElt[tt_aElt.length - 1].style.display = "none";
|
| 187 | + }
|
| 188 | +}
|
| 189 | +function tt_GetElt(id)
|
| 190 | +{
|
| 191 | + return(document.getElementById ? document.getElementById(id)
|
| 192 | + : document.all ? document.all[id]
|
| 193 | + : null);
|
| 194 | +}
|
| 195 | +function tt_GetDivW(el)
|
| 196 | +{
|
| 197 | + return(el ? (el.offsetWidth || el.style.pixelWidth || 0) : 0);
|
| 198 | +}
|
| 199 | +function tt_GetDivH(el)
|
| 200 | +{
|
| 201 | + return(el ? (el.offsetHeight || el.style.pixelHeight || 0) : 0);
|
| 202 | +}
|
| 203 | +function tt_GetScrollX()
|
| 204 | +{
|
| 205 | + return(window.pageXOffset || (tt_db ? (tt_db.scrollLeft || 0) : 0));
|
| 206 | +}
|
| 207 | +function tt_GetScrollY()
|
| 208 | +{
|
| 209 | + return(window.pageYOffset || (tt_db ? (tt_db.scrollTop || 0) : 0));
|
| 210 | +}
|
| 211 | +function tt_GetClientW()
|
| 212 | +{
|
| 213 | + return(document.body && (typeof(document.body.clientWidth) != tt_u) ? document.body.clientWidth
|
| 214 | + : (typeof(window.innerWidth) != tt_u) ? window.innerWidth
|
| 215 | + : tt_db ? (tt_db.clientWidth || 0)
|
| 216 | + : 0);
|
| 217 | +}
|
| 218 | +function tt_GetClientH()
|
| 219 | +{
|
| 220 | + // Exactly this order seems to yield correct values in all major browsers
|
| 221 | + return(document.body && (typeof(document.body.clientHeight) != tt_u) ? document.body.clientHeight
|
| 222 | + : (typeof(window.innerHeight) != tt_u) ? window.innerHeight
|
| 223 | + : tt_db ? (tt_db.clientHeight || 0)
|
| 224 | + : 0);
|
| 225 | +}
|
| 226 | +function tt_GetEvtX(e)
|
| 227 | +{
|
| 228 | + return (e ? ((typeof(e.pageX) != tt_u) ? e.pageX : (e.clientX + tt_scrlX)) : 0);
|
| 229 | +}
|
| 230 | +function tt_GetEvtY(e)
|
| 231 | +{
|
| 232 | + return (e ? ((typeof(e.pageY) != tt_u) ? e.pageY : (e.clientY + tt_scrlY)) : 0);
|
| 233 | +}
|
| 234 | +function tt_AddEvtFnc(el, sEvt, PFnc)
|
| 235 | +{
|
| 236 | + if(el)
|
| 237 | + {
|
| 238 | + if(el.addEventListener)
|
| 239 | + el.addEventListener(sEvt, PFnc, false);
|
| 240 | + else
|
| 241 | + el.attachEvent("on" + sEvt, PFnc);
|
| 242 | + }
|
| 243 | +}
|
| 244 | +function tt_RemEvtFnc(el, sEvt, PFnc)
|
| 245 | +{
|
| 246 | + if(el)
|
| 247 | + {
|
| 248 | + if(el.removeEventListener)
|
| 249 | + el.removeEventListener(sEvt, PFnc, false);
|
| 250 | + else
|
| 251 | + el.detachEvent("on" + sEvt, PFnc);
|
| 252 | + }
|
| 253 | +}
|
| 254 | +
|
| 255 | +//====================== PRIVATE ===========================================//
|
| 256 | +var tt_aExt = new Array(), // Array of extension objects
|
| 257 | +
|
| 258 | +tt_db, tt_op, tt_ie, tt_ie56, tt_bBoxOld, // Browser flags
|
| 259 | +tt_body,
|
| 260 | +tt_flagOpa, // Opacity support: 1=IE, 2=Khtml, 3=KHTML, 4=Moz, 5=W3C
|
| 261 | +tt_maxPosX, tt_maxPosY,
|
| 262 | +tt_iState = 0, // Tooltip active |= 1, shown |= 2, move with mouse |= 4
|
| 263 | +tt_opa, // Currently applied opacity
|
| 264 | +tt_bJmpVert, // Tip above mouse (or ABOVE tip below mouse)
|
| 265 | +tt_t2t, tt_t2tDad, // Tag converted to tip, and its parent element in the document
|
| 266 | +tt_elDeHref, // The tag from which Opera has removed the href attribute
|
| 267 | +// Timer
|
| 268 | +tt_tShow = new Number(0), tt_tHide = new Number(0), tt_tDurt = new Number(0),
|
| 269 | +tt_tFade = new Number(0), tt_tWaitMov = new Number(0),
|
| 270 | +tt_bWait = false,
|
| 271 | +tt_u = "undefined";
|
| 272 | +
|
| 273 | +
|
| 274 | +function tt_Init()
|
| 275 | +{
|
| 276 | + tt_MkCmdEnum();
|
| 277 | + // Send old browsers instantly to hell
|
| 278 | + if(!tt_Browser() || !tt_MkMainDiv())
|
| 279 | + return;
|
| 280 | + tt_IsW3cBox();
|
| 281 | + tt_OpaSupport();
|
| 282 | + tt_AddEvtFnc(document, "mousemove", tt_Move);
|
| 283 | + // In Debug mode we search for TagToTip() calls in order to notify
|
| 284 | + // the user if they've forgotten to set the TagsToTip config flag
|
| 285 | + if(TagsToTip || tt_Debug)
|
| 286 | + tt_SetOnloadFnc();
|
| 287 | + tt_AddEvtFnc(window, "scroll",
|
| 288 | + function()
|
| 289 | + {
|
| 290 | + tt_scrlX = tt_GetScrollX();
|
| 291 | + tt_scrlY = tt_GetScrollY();
|
| 292 | + if(tt_iState && !(tt_aV[STICKY] && (tt_iState & 2)))
|
| 293 | + tt_HideInit();
|
| 294 | + } );
|
| 295 | + // Ensure the tip be hidden when the page unloads
|
| 296 | + tt_AddEvtFnc(window, "unload", tt_Hide);
|
| 297 | + tt_Hide();
|
| 298 | +}
|
| 299 | +// Creates command names by translating config variable names to upper case
|
| 300 | +function tt_MkCmdEnum()
|
| 301 | +{
|
| 302 | + var n = 0;
|
| 303 | + for(var i in config)
|
| 304 | + eval("window." + i.toString().toUpperCase() + " = " + n++);
|
| 305 | + tt_aV.length = n;
|
| 306 | +}
|
| 307 | +function tt_Browser()
|
| 308 | +{
|
| 309 | + var n, nv, n6, w3c;
|
| 310 | +
|
| 311 | + n = navigator.userAgent.toLowerCase(),
|
| 312 | + nv = navigator.appVersion;
|
| 313 | + tt_op = (document.defaultView && typeof(eval("w" + "indow" + "." + "o" + "p" + "er" + "a")) != tt_u);
|
| 314 | + tt_ie = n.indexOf("msie") != -1 && document.all && !tt_op;
|
| 315 | + if(tt_ie)
|
| 316 | + {
|
| 317 | + var ieOld = (!document.compatMode || document.compatMode == "BackCompat");
|
| 318 | + tt_db = !ieOld ? document.documentElement : (document.body || null);
|
| 319 | + if(tt_db)
|
| 320 | + tt_ie56 = parseFloat(nv.substring(nv.indexOf("MSIE") + 5)) >= 5.5
|
| 321 | + && typeof document.body.style.maxHeight == tt_u;
|
| 322 | + }
|
| 323 | + else
|
| 324 | + {
|
| 325 | + tt_db = document.documentElement || document.body ||
|
| 326 | + (document.getElementsByTagName ? document.getElementsByTagName("body")[0]
|
| 327 | + : null);
|
| 328 | + if(!tt_op)
|
| 329 | + {
|
| 330 | + n6 = document.defaultView && typeof document.defaultView.getComputedStyle != tt_u;
|
| 331 | + w3c = !n6 && document.getElementById;
|
| 332 | + }
|
| 333 | + }
|
| 334 | + tt_body = (document.getElementsByTagName ? document.getElementsByTagName("body")[0]
|
| 335 | + : (document.body || null));
|
| 336 | + if(tt_ie || n6 || tt_op || w3c)
|
| 337 | + {
|
| 338 | + if(tt_body && tt_db)
|
| 339 | + {
|
| 340 | + if(document.attachEvent || document.addEventListener)
|
| 341 | + return true;
|
| 342 | + }
|
| 343 | + else
|
| 344 | + tt_Err("wz_tooltip.js must be included INSIDE the body section,"
|
| 345 | + + " immediately after the opening <body> tag.");
|
| 346 | + }
|
| 347 | + tt_db = null;
|
| 348 | + return false;
|
| 349 | +}
|
| 350 | +function tt_MkMainDiv()
|
| 351 | +{
|
| 352 | + // Create the tooltip DIV
|
| 353 | + if(tt_body.insertAdjacentHTML)
|
| 354 | + tt_body.insertAdjacentHTML("afterBegin", tt_MkMainDivHtm());
|
| 355 | + else if(typeof tt_body.innerHTML != tt_u && document.createElement && tt_body.appendChild)
|
| 356 | + tt_body.appendChild(tt_MkMainDivDom());
|
| 357 | + // FireFox Alzheimer bug
|
| 358 | + if(window.tt_GetMainDivRefs && tt_GetMainDivRefs())
|
| 359 | + return true;
|
| 360 | + tt_db = null;
|
| 361 | + return false;
|
| 362 | +}
|
| 363 | +function tt_MkMainDivHtm()
|
| 364 | +{
|
| 365 | + return('<div id="WzTtDiV"></div>' +
|
| 366 | + (tt_ie56 ? ('<iframe id="WzTtIfRm" src="javascript:false" scrolling="no" frameborder="0" style="filter:Alpha(opacity=0);position:absolute;top:0px;left:0px;display:none;"></iframe>')
|
| 367 | + : ''));
|
| 368 | +}
|
| 369 | +function tt_MkMainDivDom()
|
| 370 | +{
|
| 371 | + var el = document.createElement("div");
|
| 372 | + if(el)
|
| 373 | + el.id = "WzTtDiV";
|
| 374 | + return el;
|
| 375 | +}
|
| 376 | +function tt_GetMainDivRefs()
|
| 377 | +{
|
| 378 | + tt_aElt[0] = tt_GetElt("WzTtDiV");
|
| 379 | + if(tt_ie56 && tt_aElt[0])
|
| 380 | + {
|
| 381 | + tt_aElt[tt_aElt.length - 1] = tt_GetElt("WzTtIfRm");
|
| 382 | + if(!tt_aElt[tt_aElt.length - 1])
|
| 383 | + tt_aElt[0] = null;
|
| 384 | + }
|
| 385 | + if(tt_aElt[0])
|
| 386 | + {
|
| 387 | + var css = tt_aElt[0].style;
|
| 388 | +
|
| 389 | + css.visibility = "hidden";
|
| 390 | + css.position = "absolute";
|
| 391 | + css.overflow = "hidden";
|
| 392 | + return true;
|
| 393 | + }
|
| 394 | + return false;
|
| 395 | +}
|
| 396 | +function tt_ResetMainDiv()
|
| 397 | +{
|
| 398 | + var w = (window.screen && screen.width) ? screen.width : 10000;
|
| 399 | +
|
| 400 | + tt_SetTipPos(-w, 0);
|
| 401 | + tt_aElt[0].innerHTML = "";
|
| 402 | + tt_aElt[0].style.width = (w - 1) + "px";
|
| 403 | +}
|
| 404 | +function tt_IsW3cBox()
|
| 405 | +{
|
| 406 | + var css = tt_aElt[0].style;
|
| 407 | +
|
| 408 | + css.padding = "10px";
|
| 409 | + css.width = "40px";
|
| 410 | + tt_bBoxOld = (tt_GetDivW(tt_aElt[0]) == 40);
|
| 411 | + css.padding = "0px";
|
| 412 | + tt_ResetMainDiv();
|
| 413 | +}
|
| 414 | +function tt_OpaSupport()
|
| 415 | +{
|
| 416 | + var css = tt_body.style;
|
| 417 | +
|
| 418 | + tt_flagOpa = (typeof(css.filter) != tt_u) ? 1
|
| 419 | + : (typeof(css.KhtmlOpacity) != tt_u) ? 2
|
| 420 | + : (typeof(css.KHTMLOpacity) != tt_u) ? 3
|
| 421 | + : (typeof(css.MozOpacity) != tt_u) ? 4
|
| 422 | + : (typeof(css.opacity) != tt_u) ? 5
|
| 423 | + : 0;
|
| 424 | +}
|
| 425 | +// Ported from http://dean.edwards.name/weblog/2006/06/again/
|
| 426 | +// (Dean Edwards et al.)
|
| 427 | +function tt_SetOnloadFnc()
|
| 428 | +{
|
| 429 | + tt_AddEvtFnc(document, "DOMContentLoaded", tt_HideSrcTags);
|
| 430 | + tt_AddEvtFnc(window, "load", tt_HideSrcTags);
|
| 431 | + if(tt_body.attachEvent)
|
| 432 | + tt_body.attachEvent("onreadystatechange",
|
| 433 | + function() {
|
| 434 | + if(tt_body.readyState == "complete")
|
| 435 | + tt_HideSrcTags();
|
| 436 | + } );
|
| 437 | + if(/WebKit|KHTML/i.test(navigator.userAgent))
|
| 438 | + {
|
| 439 | + var t = setInterval(function() {
|
| 440 | + if(/loaded|complete/.test(document.readyState))
|
| 441 | + {
|
| 442 | + clearInterval(t);
|
| 443 | + tt_HideSrcTags();
|
| 444 | + }
|
| 445 | + }, 10);
|
| 446 | + }
|
| 447 | +}
|
| 448 | +function tt_HideSrcTags()
|
| 449 | +{
|
| 450 | + if(!window.tt_HideSrcTags || window.tt_HideSrcTags.done)
|
| 451 | + return;
|
| 452 | + window.tt_HideSrcTags.done = true;
|
| 453 | + if(!tt_HideSrcTagsRecurs(tt_body))
|
| 454 | + tt_Err("To enable the capability to convert HTML elements to tooltips,"
|
| 455 | + + " you must set TagsToTip in the global tooltip configuration"
|
| 456 | + + " to true.");
|
| 457 | +}
|
| 458 | +function tt_HideSrcTagsRecurs(dad)
|
| 459 | +{
|
| 460 | + var a, ovr, asT2t;
|
| 461 | +
|
| 462 | + // Walk the DOM tree for tags that have an onmouseover attribute
|
| 463 | + // containing a TagToTip('...') call.
|
| 464 | + // (.childNodes first since .children is bugous in Safari)
|
| 465 | + a = dad.childNodes || dad.children || null;
|
| 466 | + for(var i = a ? a.length : 0; i;)
|
| 467 | + {--i;
|
| 468 | + if(!tt_HideSrcTagsRecurs(a[i]))
|
| 469 | + return false;
|
| 470 | + ovr = a[i].getAttribute ? a[i].getAttribute("onmouseover")
|
| 471 | + : (typeof a[i].onmouseover == "function") ? a[i].onmouseover
|
| 472 | + : null;
|
| 473 | + if(ovr)
|
| 474 | + {
|
| 475 | + asT2t = ovr.toString().match(/TagToTip\s*\(\s*'[^'.]+'\s*[\),]/);
|
| 476 | + if(asT2t && asT2t.length)
|
| 477 | + {
|
| 478 | + if(!tt_HideSrcTag(asT2t[0]))
|
| 479 | + return false;
|
| 480 | + }
|
| 481 | + }
|
| 482 | + }
|
| 483 | + return true;
|
| 484 | +}
|
| 485 | +function tt_HideSrcTag(sT2t)
|
| 486 | +{
|
| 487 | + var id, el;
|
| 488 | +
|
| 489 | + // The ID passed to the found TagToTip() call identifies an HTML element
|
| 490 | + // to be converted to a tooltip, so hide that element
|
| 491 | + id = sT2t.replace(/.+'([^'.]+)'.+/, "$1");
|
| 492 | + el = tt_GetElt(id);
|
| 493 | + if(el)
|
| 494 | + {
|
| 495 | + if(tt_Debug && !TagsToTip)
|
| 496 | + return false;
|
| 497 | + else
|
| 498 | + el.style.display = "none";
|
| 499 | + }
|
| 500 | + else
|
| 501 | + tt_Err("Invalid ID\n'" + id + "'\npassed to TagToTip()."
|
| 502 | + + " There exists no HTML element with that ID.");
|
| 503 | + return true;
|
| 504 | +}
|
| 505 | +function tt_Tip(arg, t2t)
|
| 506 | +{
|
| 507 | + if(!tt_db)
|
| 508 | + return;
|
| 509 | + if(tt_iState)
|
| 510 | + tt_Hide();
|
| 511 | + if(!tt_Enabled)
|
| 512 | + return;
|
| 513 | + tt_t2t = t2t;
|
| 514 | + if(!tt_ReadCmds(arg))
|
| 515 | + return;
|
| 516 | + tt_iState = 0x1 | 0x4;
|
| 517 | + tt_AdaptConfig1();
|
| 518 | + tt_MkTipContent(arg);
|
| 519 | + tt_MkTipSubDivs();
|
| 520 | + tt_FormatTip();
|
| 521 | + tt_bJmpVert = false;
|
| 522 | + tt_maxPosX = tt_GetClientW() + tt_scrlX - tt_w - 1;
|
| 523 | + tt_maxPosY = tt_GetClientH() + tt_scrlY - tt_h - 1;
|
| 524 | + tt_AdaptConfig2();
|
| 525 | + // We must fake the first mousemove in order to ensure the tip
|
| 526 | + // be immediately shown and positioned
|
| 527 | + tt_Move();
|
| 528 | + tt_ShowInit();
|
| 529 | +}
|
| 530 | +function tt_ReadCmds(a)
|
| 531 | +{
|
| 532 | + var i;
|
| 533 | +
|
| 534 | + // First load the global config values, to initialize also values
|
| 535 | + // for which no command has been passed
|
| 536 | + i = 0;
|
| 537 | + for(var j in config)
|
| 538 | + tt_aV[i++] = config[j];
|
| 539 | + // Then replace each cached config value for which a command has been
|
| 540 | + // passed (ensure the # of command args plus value args be even)
|
| 541 | + if(a.length & 1)
|
| 542 | + {
|
| 543 | + for(i = a.length - 1; i > 0; i -= 2)
|
| 544 | + tt_aV[a[i - 1]] = a[i];
|
| 545 | + return true;
|
| 546 | + }
|
| 547 | + tt_Err("Incorrect call of Tip() or TagToTip().\n"
|
| 548 | + + "Each command must be followed by a value.");
|
| 549 | + return false;
|
| 550 | +}
|
| 551 | +function tt_AdaptConfig1()
|
| 552 | +{
|
| 553 | + tt_ExtCallFncs(0, "LoadConfig");
|
| 554 | + // Inherit unspecified title formattings from body
|
| 555 | + if(!tt_aV[TITLEBGCOLOR].length)
|
| 556 | + tt_aV[TITLEBGCOLOR] = tt_aV[BORDERCOLOR];
|
| 557 | + if(!tt_aV[TITLEFONTCOLOR].length)
|
| 558 | + tt_aV[TITLEFONTCOLOR] = tt_aV[BGCOLOR];
|
| 559 | + if(!tt_aV[TITLEFONTFACE].length)
|
| 560 | + tt_aV[TITLEFONTFACE] = tt_aV[FONTFACE];
|
| 561 | + if(!tt_aV[TITLEFONTSIZE].length)
|
| 562 | + tt_aV[TITLEFONTSIZE] = tt_aV[FONTSIZE];
|
| 563 | + if(tt_aV[CLOSEBTN])
|
| 564 | + {
|
| 565 | + // Use title colors for non-specified closebutton colors
|
| 566 | + if(!tt_aV[CLOSEBTNCOLORS])
|
| 567 | + tt_aV[CLOSEBTNCOLORS] = new Array("", "", "", "");
|
| 568 | + for(var i = 4; i;)
|
| 569 | + {--i;
|
| 570 | + if(!tt_aV[CLOSEBTNCOLORS][i].length)
|
| 571 | + tt_aV[CLOSEBTNCOLORS][i] = (i & 1) ? tt_aV[TITLEFONTCOLOR] : tt_aV[TITLEBGCOLOR];
|
| 572 | + }
|
| 573 | + // Enforce titlebar be shown
|
| 574 | + if(!tt_aV[TITLE].length)
|
| 575 | + tt_aV[TITLE] = " ";
|
| 576 | + }
|
| 577 | + // Circumvents broken display of images and fade-in flicker in Geckos < 1.8
|
| 578 | + if(tt_aV[OPACITY] == 100 && typeof tt_aElt[0].style.MozOpacity != tt_u && !Array.every)
|
| 579 | + tt_aV[OPACITY] = 99;
|
| 580 | + // Smartly shorten the delay for fade-in tooltips
|
| 581 | + if(tt_aV[FADEIN] && tt_flagOpa && tt_aV[DELAY] > 100)
|
| 582 | + tt_aV[DELAY] = Math.max(tt_aV[DELAY] - tt_aV[FADEIN], 100);
|
| 583 | +}
|
| 584 | +function tt_AdaptConfig2()
|
| 585 | +{
|
| 586 | + if(tt_aV[CENTERMOUSE])
|
| 587 | + tt_aV[OFFSETX] -= ((tt_w - (tt_aV[SHADOW] ? tt_aV[SHADOWWIDTH] : 0)) >> 1);
|
| 588 | +}
|
| 589 | +// Expose content globally so extensions can modify it
|
| 590 | +function tt_MkTipContent(a)
|
| 591 | +{
|
| 592 | + if(tt_t2t)
|
| 593 | + {
|
| 594 | + if(tt_aV[COPYCONTENT])
|
| 595 | + tt_sContent = tt_t2t.innerHTML;
|
| 596 | + else
|
| 597 | + tt_sContent = "";
|
| 598 | + }
|
| 599 | + else
|
| 600 | + tt_sContent = a[0];
|
| 601 | + tt_ExtCallFncs(0, "CreateContentString");
|
| 602 | +}
|
| 603 | +function tt_MkTipSubDivs()
|
| 604 | +{
|
| 605 | + var sCss = 'position:relative;margin:0px;padding:0px;border-width:0px;left:0px;top:0px;line-height:normal;width:auto;',
|
| 606 | + sTbTrTd = ' cellspacing=0 cellpadding=0 border=0 style="' + sCss + '"><tbody style="' + sCss + '"><tr><td ';
|
| 607 | +
|
| 608 | + tt_aElt[0].innerHTML =
|
| 609 | + (''
|
| 610 | + + (tt_aV[TITLE].length ?
|
| 611 | + ('<div id="WzTiTl" style="position:relative;z-index:1;">'
|
| 612 | + + '<table id="WzTiTlTb"' + sTbTrTd + 'id="WzTiTlI" style="' + sCss + '">'
|
| 613 | + + tt_aV[TITLE]
|
| 614 | + + '</td>'
|
| 615 | + + (tt_aV[CLOSEBTN] ?
|
| 616 | + ('<td align="right" style="' + sCss
|
| 617 | + + 'text-align:right;">'
|
| 618 | + + '<span id="WzClOsE" style="padding-left:2px;padding-right:2px;'
|
| 619 | + + 'cursor:' + (tt_ie ? 'hand' : 'pointer')
|
| 620 | + + ';" onmouseover="tt_OnCloseBtnOver(1)" onmouseout="tt_OnCloseBtnOver(0)" onclick="tt_HideInit()">'
|
| 621 | + + tt_aV[CLOSEBTNTEXT]
|
| 622 | + + '</span></td>')
|
| 623 | + : '')
|
| 624 | + + '</tr></tbody></table></div>')
|
| 625 | + : '')
|
| 626 | + + '<div id="WzBoDy" style="position:relative;z-index:0;">'
|
| 627 | + + '<table' + sTbTrTd + 'id="WzBoDyI" style="' + sCss + '">'
|
| 628 | + + tt_sContent
|
| 629 | + + '</td></tr></tbody></table></div>'
|
| 630 | + + (tt_aV[SHADOW]
|
| 631 | + ? ('<div id="WzTtShDwR" style="position:absolute;overflow:hidden;"></div>'
|
| 632 | + + '<div id="WzTtShDwB" style="position:relative;overflow:hidden;"></div>')
|
| 633 | + : '')
|
| 634 | + );
|
| 635 | + tt_GetSubDivRefs();
|
| 636 | + // Convert DOM node to tip
|
| 637 | + if(tt_t2t && !tt_aV[COPYCONTENT])
|
| 638 | + {
|
| 639 | + // Store the tag's parent element so we can restore that DOM branch
|
| 640 | + // once the tooltip is hidden
|
| 641 | + tt_t2tDad = tt_t2t.parentNode || tt_t2t.parentElement || tt_t2t.offsetParent || null;
|
| 642 | + if(tt_t2tDad)
|
| 643 | + {
|
| 644 | + tt_MovDomNode(tt_t2t, tt_t2tDad, tt_aElt[6]);
|
| 645 | + tt_t2t.style.display = "block";
|
| 646 | + }
|
| 647 | + }
|
| 648 | + tt_ExtCallFncs(0, "SubDivsCreated");
|
| 649 | +}
|
| 650 | +function tt_GetSubDivRefs()
|
| 651 | +{
|
| 652 | + var aId = new Array("WzTiTl", "WzTiTlTb", "WzTiTlI", "WzClOsE", "WzBoDy", "WzBoDyI", "WzTtShDwB", "WzTtShDwR");
|
| 653 | +
|
| 654 | + for(var i = aId.length; i; --i)
|
| 655 | + tt_aElt[i] = tt_GetElt(aId[i - 1]);
|
| 656 | +}
|
| 657 | +function tt_FormatTip()
|
| 658 | +{
|
| 659 | + var css, w, iOffY, iOffSh;
|
| 660 | +
|
| 661 | + //--------- Title DIV ----------
|
| 662 | + if(tt_aV[TITLE].length)
|
| 663 | + {
|
| 664 | + css = tt_aElt[1].style;
|
| 665 | + css.background = tt_aV[TITLEBGCOLOR];
|
| 666 | + css.paddingTop = (tt_aV[CLOSEBTN] ? 2 : 0) + "px";
|
| 667 | + css.paddingBottom = "1px";
|
| 668 | + css.paddingLeft = css.paddingRight = tt_aV[PADDING] + "px";
|
| 669 | + css = tt_aElt[3].style;
|
| 670 | + css.color = tt_aV[TITLEFONTCOLOR];
|
| 671 | + css.fontFamily = tt_aV[TITLEFONTFACE];
|
| 672 | + css.fontSize = tt_aV[TITLEFONTSIZE];
|
| 673 | + css.fontWeight = "bold";
|
| 674 | + css.textAlign = tt_aV[TITLEALIGN];
|
| 675 | + // Close button DIV
|
| 676 | + if(tt_aElt[4])
|
| 677 | + {
|
| 678 | + css.paddingRight = (tt_aV[PADDING] << 1) + "px";
|
| 679 | + css = tt_aElt[4].style;
|
| 680 | + css.background = tt_aV[CLOSEBTNCOLORS][0];
|
| 681 | + css.color = tt_aV[CLOSEBTNCOLORS][1];
|
| 682 | + css.fontFamily = tt_aV[TITLEFONTFACE];
|
| 683 | + css.fontSize = tt_aV[TITLEFONTSIZE];
|
| 684 | + css.fontWeight = "bold";
|
| 685 | + }
|
| 686 | + if(tt_aV[WIDTH] > 0)
|
| 687 | + tt_w = tt_aV[WIDTH] + ((tt_aV[PADDING] + tt_aV[BORDERWIDTH]) << 1);
|
| 688 | + else
|
| 689 | + {
|
| 690 | + tt_w = tt_GetDivW(tt_aElt[3]) + tt_GetDivW(tt_aElt[4]);
|
| 691 | + // Some spacing between title DIV and closebutton
|
| 692 | + if(tt_aElt[4])
|
| 693 | + tt_w += tt_aV[PADDING];
|
| 694 | + }
|
| 695 | + // Ensure the top border of the body DIV be covered by the title DIV
|
| 696 | + iOffY = -tt_aV[BORDERWIDTH];
|
| 697 | + }
|
| 698 | + else
|
| 699 | + {
|
| 700 | + tt_w = 0;
|
| 701 | + iOffY = 0;
|
| 702 | + }
|
| 703 | +
|
| 704 | + //-------- Body DIV ------------
|
| 705 | + css = tt_aElt[5].style;
|
| 706 | + css.top = iOffY + "px";
|
| 707 | + if(tt_aV[BORDERWIDTH])
|
| 708 | + {
|
| 709 | + css.borderColor = tt_aV[BORDERCOLOR];
|
| 710 | + css.borderStyle = tt_aV[BORDERSTYLE];
|
| 711 | + css.borderWidth = tt_aV[BORDERWIDTH] + "px";
|
| 712 | + }
|
| 713 | + if(tt_aV[BGCOLOR].length)
|
| 714 | + css.background = tt_aV[BGCOLOR];
|
| 715 | + if(tt_aV[BGIMG].length)
|
| 716 | + css.backgroundImage = "url(" + tt_aV[BGIMG] + ")";
|
| 717 | + css.padding = tt_aV[PADDING] + "px";
|
| 718 | + css.textAlign = tt_aV[TEXTALIGN];
|
| 719 | + // TD inside body DIV
|
| 720 | + css = tt_aElt[6].style;
|
| 721 | + css.color = tt_aV[FONTCOLOR];
|
| 722 | + css.fontFamily = tt_aV[FONTFACE];
|
| 723 | + css.fontSize = tt_aV[FONTSIZE];
|
| 724 | + css.fontWeight = tt_aV[FONTWEIGHT];
|
| 725 | + css.background = "";
|
| 726 | + css.textAlign = tt_aV[TEXTALIGN];
|
| 727 | + if(tt_aV[WIDTH] > 0)
|
| 728 | + w = tt_aV[WIDTH] + ((tt_aV[PADDING] + tt_aV[BORDERWIDTH]) << 1);
|
| 729 | + else
|
| 730 | + // We measure the width of the body's inner TD, because some browsers
|
| 731 | + // expand the width of the container and outer body DIV to 100%
|
| 732 | + w = tt_GetDivW(tt_aElt[6]) + ((tt_aV[PADDING] + tt_aV[BORDERWIDTH]) << 1);
|
| 733 | + if(w > tt_w)
|
| 734 | + tt_w = w;
|
| 735 | +
|
| 736 | + //--------- Shadow DIVs ------------
|
| 737 | + if(tt_aV[SHADOW])
|
| 738 | + {
|
| 739 | + tt_w += tt_aV[SHADOWWIDTH];
|
| 740 | + iOffSh = Math.floor((tt_aV[SHADOWWIDTH] * 4) / 3);
|
| 741 | + // Bottom shadow
|
| 742 | + css = tt_aElt[7].style;
|
| 743 | + css.top = iOffY + "px";
|
| 744 | + css.left = iOffSh + "px";
|
| 745 | + css.width = (tt_w - iOffSh - tt_aV[SHADOWWIDTH]) + "px";
|
| 746 | + css.height = tt_aV[SHADOWWIDTH] + "px";
|
| 747 | + css.background = tt_aV[SHADOWCOLOR];
|
| 748 | + // Right shadow
|
| 749 | + css = tt_aElt[8].style;
|
| 750 | + css.top = iOffSh + "px";
|
| 751 | + css.left = (tt_w - tt_aV[SHADOWWIDTH]) + "px";
|
| 752 | + css.width = tt_aV[SHADOWWIDTH] + "px";
|
| 753 | + css.background = tt_aV[SHADOWCOLOR];
|
| 754 | + }
|
| 755 | + else
|
| 756 | + iOffSh = 0;
|
| 757 | +
|
| 758 | + //-------- Container DIV -------
|
| 759 | + tt_SetTipOpa(tt_aV[FADEIN] ? 0 : tt_aV[OPACITY]);
|
| 760 | + tt_FixSize(iOffY, iOffSh);
|
| 761 | +}
|
| 762 | +// Fixate the size so it can't dynamically change while the tooltip is moving.
|
| 763 | +function tt_FixSize(iOffY, iOffSh)
|
| 764 | +{
|
| 765 | + var wIn, wOut, i;
|
| 766 | +
|
| 767 | + tt_aElt[0].style.width = tt_w + "px";
|
| 768 | + tt_aElt[0].style.pixelWidth = tt_w;
|
| 769 | + wOut = tt_w - ((tt_aV[SHADOW]) ? tt_aV[SHADOWWIDTH] : 0);
|
| 770 | + // Body
|
| 771 | + wIn = wOut;
|
| 772 | + if(!tt_bBoxOld)
|
| 773 | + wIn -= ((tt_aV[PADDING] + tt_aV[BORDERWIDTH]) << 1);
|
| 774 | + tt_aElt[5].style.width = wIn + "px";
|
| 775 | + // Title
|
| 776 | + if(tt_aElt[1])
|
| 777 | + {
|
| 778 | + wIn = wOut - (tt_aV[PADDING] << 1);
|
| 779 | + if(!tt_bBoxOld)
|
| 780 | + wOut = wIn;
|
| 781 | + tt_aElt[1].style.width = wOut + "px";
|
| 782 | + tt_aElt[2].style.width = wIn + "px";
|
| 783 | + }
|
| 784 | + tt_h = tt_GetDivH(tt_aElt[0]) + iOffY;
|
| 785 | + // Right shadow
|
| 786 | + if(tt_aElt[8])
|
| 787 | + tt_aElt[8].style.height = (tt_h - iOffSh) + "px";
|
| 788 | + i = tt_aElt.length - 1;
|
| 789 | + if(tt_aElt[i])
|
| 790 | + {
|
| 791 | + tt_aElt[i].style.width = tt_w + "px";
|
| 792 | + tt_aElt[i].style.height = tt_h + "px";
|
| 793 | + }
|
| 794 | +}
|
| 795 | +function tt_DeAlt(el)
|
| 796 | +{
|
| 797 | + var aKid;
|
| 798 | +
|
| 799 | + if(el.alt)
|
| 800 | + el.alt = "";
|
| 801 | + if(el.title)
|
| 802 | + el.title = "";
|
| 803 | + aKid = el.childNodes || el.children || null;
|
| 804 | + if(aKid)
|
| 805 | + {
|
| 806 | + for(var i = aKid.length; i;)
|
| 807 | + tt_DeAlt(aKid[--i]);
|
| 808 | + }
|
| 809 | +}
|
| 810 | +// This hack removes the annoying native tooltips over links in Opera
|
| 811 | +function tt_OpDeHref(el)
|
| 812 | +{
|
| 813 | + if(!tt_op)
|
| 814 | + return;
|
| 815 | + if(tt_elDeHref)
|
| 816 | + tt_OpReHref();
|
| 817 | + while(el)
|
| 818 | + {
|
| 819 | + if(el.hasAttribute("href"))
|
| 820 | + {
|
| 821 | + el.t_href = el.getAttribute("href");
|
| 822 | + el.t_stats = window.status;
|
| 823 | + el.removeAttribute("href");
|
| 824 | + el.style.cursor = "hand";
|
| 825 | + tt_AddEvtFnc(el, "mousedown", tt_OpReHref);
|
| 826 | + window.status = el.t_href;
|
| 827 | + tt_elDeHref = el;
|
| 828 | + break;
|
| 829 | + }
|
| 830 | + el = el.parentElement;
|
| 831 | + }
|
| 832 | +}
|
| 833 | +function tt_ShowInit()
|
| 834 | +{
|
| 835 | + tt_tShow.Timer("tt_Show()", tt_aV[DELAY], true);
|
| 836 | + if(tt_aV[CLICKCLOSE])
|
| 837 | + tt_AddEvtFnc(document, "mouseup", tt_HideInit);
|
| 838 | +}
|
| 839 | +function tt_OverInit(e)
|
| 840 | +{
|
| 841 | + tt_over = e.target || e.srcElement;
|
| 842 | + tt_DeAlt(tt_over);
|
| 843 | + tt_OpDeHref(tt_over);
|
| 844 | + tt_AddRemOutFnc(true);
|
| 845 | +}
|
| 846 | +function tt_Show()
|
| 847 | +{
|
| 848 | + var css = tt_aElt[0].style;
|
| 849 | +
|
| 850 | + // Override the z-index of the topmost wz_dragdrop.js D&D item
|
| 851 | + css.zIndex = Math.max((window.dd && dd.z) ? (dd.z + 2) : 0, 1010);
|
| 852 | + if(tt_aV[STICKY] || !tt_aV[FOLLOWMOUSE])
|
| 853 | + tt_iState &= ~0x4;
|
| 854 | + if(tt_aV[DURATION] > 0)
|
| 855 | + tt_tDurt.Timer("tt_HideInit()", tt_aV[DURATION], true);
|
| 856 | + tt_ExtCallFncs(0, "Show")
|
| 857 | + css.visibility = "visible";
|
| 858 | + tt_iState |= 0x2;
|
| 859 | + if(tt_aV[FADEIN])
|
| 860 | + tt_Fade(0, 0, tt_aV[OPACITY], Math.round(tt_aV[FADEIN] / tt_aV[FADEINTERVAL]));
|
| 861 | + tt_ShowIfrm();
|
| 862 | +}
|
| 863 | +function tt_ShowIfrm()
|
| 864 | +{
|
| 865 | + if(tt_ie56)
|
| 866 | + {
|
| 867 | + var ifrm = tt_aElt[tt_aElt.length - 1];
|
| 868 | + if(ifrm)
|
| 869 | + {
|
| 870 | + var css = ifrm.style;
|
| 871 | + css.zIndex = tt_aElt[0].style.zIndex - 1;
|
| 872 | + css.display = "block";
|
| 873 | + }
|
| 874 | + }
|
| 875 | +}
|
| 876 | +function tt_Move(e)
|
| 877 | +{
|
| 878 | + e = window.event || e;
|
| 879 | + if(e)
|
| 880 | + {
|
| 881 | + tt_musX = tt_GetEvtX(e);
|
| 882 | + tt_musY = tt_GetEvtY(e);
|
| 883 | + }
|
| 884 | + if(tt_iState)
|
| 885 | + {
|
| 886 | + if(!tt_over && e)
|
| 887 | + tt_OverInit(e);
|
| 888 | + if(tt_iState & 0x4)
|
| 889 | + {
|
| 890 | + // Protect some browsers against jam of mousemove events
|
| 891 | + if(!tt_op && !tt_ie)
|
| 892 | + {
|
| 893 | + if(tt_bWait)
|
| 894 | + return;
|
| 895 | + tt_bWait = true;
|
| 896 | + tt_tWaitMov.Timer("tt_bWait = false;", 1, true);
|
| 897 | + }
|
| 898 | + if(tt_aV[FIX])
|
| 899 | + {
|
| 900 | + tt_iState &= ~0x4;
|
| 901 | + tt_SetTipPos(tt_aV[FIX][0], tt_aV[FIX][1]);
|
| 902 | + }
|
| 903 | + else if(!tt_ExtCallFncs(e, "MoveBefore"))
|
| 904 | + tt_SetTipPos(tt_PosX(), tt_PosY());
|
| 905 | + tt_ExtCallFncs([tt_musX, tt_musY], "MoveAfter")
|
| 906 | + }
|
| 907 | + }
|
| 908 | +}
|
| 909 | +function tt_PosX()
|
| 910 | +{
|
| 911 | + var x;
|
| 912 | +
|
| 913 | + x = tt_musX;
|
| 914 | + if(tt_aV[LEFT])
|
| 915 | + x -= tt_w + tt_aV[OFFSETX] - (tt_aV[SHADOW] ? tt_aV[SHADOWWIDTH] : 0);
|
| 916 | + else
|
| 917 | + x += tt_aV[OFFSETX];
|
| 918 | + // Prevent tip from extending past right/left clientarea boundary
|
| 919 | + if(x > tt_maxPosX)
|
| 920 | + x = tt_maxPosX;
|
| 921 | + return((x < tt_scrlX) ? tt_scrlX : x);
|
| 922 | +}
|
| 923 | +function tt_PosY()
|
| 924 | +{
|
| 925 | + var y;
|
| 926 | +
|
| 927 | + // Apply some hysteresis after the tip has snapped to the other side of the
|
| 928 | + // mouse. In case of insufficient space above and below the mouse, we place
|
| 929 | + // the tip below.
|
| 930 | + if(tt_aV[ABOVE] && (!tt_bJmpVert || tt_CalcPosYAbove() >= tt_scrlY + 16))
|
| 931 | + y = tt_DoPosYAbove();
|
| 932 | + else if(!tt_aV[ABOVE] && tt_bJmpVert && tt_CalcPosYBelow() > tt_maxPosY - 16)
|
| 933 | + y = tt_DoPosYAbove();
|
| 934 | + else
|
| 935 | + y = tt_DoPosYBelow();
|
| 936 | + // Snap to other side of mouse if tip would extend past window boundary
|
| 937 | + if(y > tt_maxPosY)
|
| 938 | + y = tt_DoPosYAbove();
|
| 939 | + if(y < tt_scrlY)
|
| 940 | + y = tt_DoPosYBelow();
|
| 941 | + return y;
|
| 942 | +}
|
| 943 | +function tt_DoPosYBelow()
|
| 944 | +{
|
| 945 | + tt_bJmpVert = tt_aV[ABOVE];
|
| 946 | + return tt_CalcPosYBelow();
|
| 947 | +}
|
| 948 | +function tt_DoPosYAbove()
|
| 949 | +{
|
| 950 | + tt_bJmpVert = !tt_aV[ABOVE];
|
| 951 | + return tt_CalcPosYAbove();
|
| 952 | +}
|
| 953 | +function tt_CalcPosYBelow()
|
| 954 | +{
|
| 955 | + return(tt_musY + tt_aV[OFFSETY]);
|
| 956 | +}
|
| 957 | +function tt_CalcPosYAbove()
|
| 958 | +{
|
| 959 | + var dy = tt_aV[OFFSETY] - (tt_aV[SHADOW] ? tt_aV[SHADOWWIDTH] : 0);
|
| 960 | + if(tt_aV[OFFSETY] > 0 && dy <= 0)
|
| 961 | + dy = 1;
|
| 962 | + return(tt_musY - tt_h - dy);
|
| 963 | +}
|
| 964 | +function tt_OnOut()
|
| 965 | +{
|
| 966 | + tt_AddRemOutFnc(false);
|
| 967 | + if(!(tt_aV[STICKY] && (tt_iState & 0x2)))
|
| 968 | + tt_HideInit();
|
| 969 | +}
|
| 970 | +function tt_HideInit()
|
| 971 | +{
|
| 972 | + tt_ExtCallFncs(0, "HideInit");
|
| 973 | + tt_iState &= ~0x4;
|
| 974 | + if(tt_flagOpa && tt_aV[FADEOUT])
|
| 975 | + {
|
| 976 | + tt_tFade.EndTimer();
|
| 977 | + if(tt_opa)
|
| 978 | + {
|
| 979 | + var n = Math.round(tt_aV[FADEOUT] / (tt_aV[FADEINTERVAL] * (tt_aV[OPACITY] / tt_opa)));
|
| 980 | + tt_Fade(tt_opa, tt_opa, 0, n);
|
| 981 | + return;
|
| 982 | + }
|
| 983 | + }
|
| 984 | + tt_tHide.Timer("tt_Hide();", 1, false);
|
| 985 | +}
|
| 986 | +function tt_OpReHref()
|
| 987 | +{
|
| 988 | + if(tt_elDeHref)
|
| 989 | + {
|
| 990 | + tt_elDeHref.setAttribute("href", tt_elDeHref.t_href);
|
| 991 | + tt_RemEvtFnc(tt_elDeHref, "mousedown", tt_OpReHref);
|
| 992 | + window.status = tt_elDeHref.t_stats;
|
| 993 | + tt_elDeHref = null;
|
| 994 | + }
|
| 995 | +}
|
| 996 | +function tt_Fade(a, now, z, n)
|
| 997 | +{
|
| 998 | + if(n)
|
| 999 | + {
|
| 1000 | + now += Math.round((z - now) / n);
|
| 1001 | + if((z > a) ? (now >= z) : (now <= z))
|
| 1002 | + now = z;
|
| 1003 | + else
|
| 1004 | + tt_tFade.Timer("tt_Fade("
|
| 1005 | + + a + "," + now + "," + z + "," + (n - 1)
|
| 1006 | + + ")",
|
| 1007 | + tt_aV[FADEINTERVAL],
|
| 1008 | + true);
|
| 1009 | + }
|
| 1010 | + now ? tt_SetTipOpa(now) : tt_Hide();
|
| 1011 | +}
|
| 1012 | +// To circumvent the opacity nesting flaws of IE, we set the opacity
|
| 1013 | +// for each sub-DIV separately, rather than for the container DIV.
|
| 1014 | +function tt_SetTipOpa(opa)
|
| 1015 | +{
|
| 1016 | + tt_SetOpa(tt_aElt[5].style, opa);
|
| 1017 | + if(tt_aElt[1])
|
| 1018 | + tt_SetOpa(tt_aElt[1].style, opa);
|
| 1019 | + if(tt_aV[SHADOW])
|
| 1020 | + {
|
| 1021 | + opa = Math.round(opa * 0.8);
|
| 1022 | + tt_SetOpa(tt_aElt[7].style, opa);
|
| 1023 | + tt_SetOpa(tt_aElt[8].style, opa);
|
| 1024 | + }
|
| 1025 | +}
|
| 1026 | +function tt_OnCloseBtnOver(iOver)
|
| 1027 | +{
|
| 1028 | + var css = tt_aElt[4].style;
|
| 1029 | +
|
| 1030 | + iOver <<= 1;
|
| 1031 | + css.background = tt_aV[CLOSEBTNCOLORS][iOver];
|
| 1032 | + css.color = tt_aV[CLOSEBTNCOLORS][iOver + 1];
|
| 1033 | +}
|
| 1034 | +function tt_Int(x)
|
| 1035 | +{
|
| 1036 | + var y;
|
| 1037 | +
|
| 1038 | + return(isNaN(y = parseInt(x)) ? 0 : y);
|
| 1039 | +}
|
| 1040 | +// Adds or removes the document.mousemove or HoveredElem.mouseout handler
|
| 1041 | +// conveniently. Keeps track of those handlers to prevent them from being
|
| 1042 | +// set or removed redundantly.
|
| 1043 | +function tt_AddRemOutFnc(bAdd)
|
| 1044 | +{
|
| 1045 | + var PSet = bAdd ? tt_AddEvtFnc : tt_RemEvtFnc;
|
| 1046 | +
|
| 1047 | + if(bAdd != tt_AddRemOutFnc.bOn)
|
| 1048 | + {
|
| 1049 | + PSet(tt_over, "mouseout", tt_OnOut);
|
| 1050 | + tt_AddRemOutFnc.bOn = bAdd;
|
| 1051 | + if(!bAdd)
|
| 1052 | + tt_OpReHref();
|
| 1053 | + }
|
| 1054 | +}
|
| 1055 | +tt_AddRemOutFnc.bOn = false;
|
| 1056 | +Number.prototype.Timer = function(s, iT, bUrge)
|
| 1057 | +{
|
| 1058 | + if(!this.value || bUrge)
|
| 1059 | + this.value = window.setTimeout(s, iT);
|
| 1060 | +}
|
| 1061 | +Number.prototype.EndTimer = function()
|
| 1062 | +{
|
| 1063 | + if(this.value)
|
| 1064 | + {
|
| 1065 | + window.clearTimeout(this.value);
|
| 1066 | + this.value = 0;
|
| 1067 | + }
|
| 1068 | +}
|
| 1069 | +function tt_SetOpa(css, opa)
|
| 1070 | +{
|
| 1071 | + tt_opa = opa;
|
| 1072 | + if(tt_flagOpa == 1)
|
| 1073 | + {
|
| 1074 | + // Hack for bugs of IE:
|
| 1075 | + // A DIV cannot be made visible in a single step if an opacity < 100
|
| 1076 | + // has been applied while the DIV was hidden.
|
| 1077 | + // Moreover, in IE6, applying an opacity < 100 has no effect if the
|
| 1078 | + // concerned element has no layout (position, size, zoom, ...).
|
| 1079 | + if(opa < 100)
|
| 1080 | + {
|
| 1081 | + var bVis = css.visibility != "hidden";
|
| 1082 | + css.zoom = "100%";
|
| 1083 | + if(!bVis)
|
| 1084 | + css.visibility = "visible";
|
| 1085 | + css.filter = "alpha(opacity=" + opa + ")";
|
| 1086 | + if(!bVis)
|
| 1087 | + css.visibility = "hidden";
|
| 1088 | + }
|
| 1089 | + else
|
| 1090 | + css.filter = "";
|
| 1091 | + }
|
| 1092 | + else
|
| 1093 | + {
|
| 1094 | + opa /= 100.0;
|
| 1095 | + switch(tt_flagOpa)
|
| 1096 | + {
|
| 1097 | + case 2:
|
| 1098 | + css.KhtmlOpacity = opa; break;
|
| 1099 | + case 3:
|
| 1100 | + css.KHTMLOpacity = opa; break;
|
| 1101 | + case 4:
|
| 1102 | + css.MozOpacity = opa; break;
|
| 1103 | + case 5:
|
| 1104 | + css.opacity = opa; break;
|
| 1105 | + }
|
| 1106 | + }
|
| 1107 | +}
|
| 1108 | +function tt_MovDomNode(el, dadFrom, dadTo)
|
| 1109 | +{
|
| 1110 | + if(dadFrom)
|
| 1111 | + dadFrom.removeChild(el);
|
| 1112 | + if(dadTo)
|
| 1113 | + dadTo.appendChild(el);
|
| 1114 | +}
|
| 1115 | +function tt_Err(sErr)
|
| 1116 | +{
|
| 1117 | + if(tt_Debug)
|
| 1118 | + alert("Tooltip Script Error Message:\n\n" + sErr);
|
| 1119 | +}
|
| 1120 | +
|
| 1121 | +//=========== DEALING WITH EXTENSIONS ==============//
|
| 1122 | +function tt_ExtCmdEnum()
|
| 1123 | +{
|
| 1124 | + var s;
|
| 1125 | +
|
| 1126 | + // Add new command(s) to the commands enum
|
| 1127 | + for(var i in config)
|
| 1128 | + {
|
| 1129 | + s = "window." + i.toString().toUpperCase();
|
| 1130 | + if(eval("typeof(" + s + ") == tt_u"))
|
| 1131 | + {
|
| 1132 | + eval(s + " = " + tt_aV.length);
|
| 1133 | + tt_aV[tt_aV.length] = null;
|
| 1134 | + }
|
| 1135 | + }
|
| 1136 | +}
|
| 1137 | +function tt_ExtCallFncs(arg, sFnc)
|
| 1138 | +{
|
| 1139 | + var b = false;
|
| 1140 | + for(var i = tt_aExt.length; i;)
|
| 1141 | + {--i;
|
| 1142 | + var fnc = tt_aExt[i]["On" + sFnc];
|
| 1143 | + // Call the method the extension has defined for this event
|
| 1144 | + if(fnc && fnc(arg))
|
| 1145 | + b = true;
|
| 1146 | + }
|
| 1147 | + return b;
|
| 1148 | +}
|
| 1149 | +
|
| 1150 | +tt_Init();
|
Index: trunk/extensions/SemanticNotifyMe/scripts/NotifyMe/NotifyHelper.js |
— | — | @@ -0,0 +1,564 @@ |
| 2 | +/** *****************************************************************************
|
| 3 | +* NotifyMe helper for Semantic MediaWiki
|
| 4 | +* Developed by Dch <hehehu@gmail.com>
|
| 5 | +*
|
| 6 | +* NotifyHelper.js
|
| 7 | +* Manages major functionalities and GUI of the User Notification Interface
|
| 8 | +* @author Dch [hehehu@gmail.com]
|
| 9 | +*/
|
| 10 | +
|
| 11 | +var notifyhelper = null;
|
| 12 | +
|
| 13 | +var NotifyHelper = Class.create();
|
| 14 | +NotifyHelper.prototype = {
|
| 15 | +
|
| 16 | +/**
|
| 17 | +* Initialize the NotifyHelper object and all variables
|
| 18 | +*/
|
| 19 | +initialize:function(){
|
| 20 | + this.pendingElement = null;
|
| 21 | +},
|
| 22 | +
|
| 23 | +/**
|
| 24 | +* Called whenever notify me manager is minimized or maximized
|
| 25 | +*/
|
| 26 | +switchlayout:function(){
|
| 27 | + if($("layoutcontent").style.display == "none"){
|
| 28 | + $("layoutcontent").style.display = "";
|
| 29 | + $("layouttitle-link").removeClassName("plusminus");
|
| 30 | + $("layouttitle-link").addClassName("minusplus");
|
| 31 | + }
|
| 32 | + else {
|
| 33 | + $("layoutcontent").style.display = "none";
|
| 34 | + $("layouttitle-link").removeClassName("minusplus");
|
| 35 | + $("layouttitle-link").addClassName("plusminus");
|
| 36 | + }
|
| 37 | +},
|
| 38 | +/**
|
| 39 | +* Called whenever notify me query is minimized or maximized
|
| 40 | +*/
|
| 41 | +switchquery:function(){
|
| 42 | + if($("querycontent").style.display == "none"){
|
| 43 | + $("querycontent").style.display = "";
|
| 44 | + $("querytitle-link").removeClassName("plusminus");
|
| 45 | + $("querytitle-link").addClassName("minusplus");
|
| 46 | + }
|
| 47 | + else {
|
| 48 | + $("querycontent").style.display = "none";
|
| 49 | + $("querytitle-link").removeClassName("minusplus");
|
| 50 | + $("querytitle-link").addClassName("plusminus");
|
| 51 | + }
|
| 52 | +},
|
| 53 | +
|
| 54 | +/**
|
| 55 | +* Executes a save
|
| 56 | +*/
|
| 57 | +doSaveToNotify:function(){
|
| 58 | + /*STARTLOG*/
|
| 59 | + if(window.smwhgLogger){
|
| 60 | + smwhgLogger.log("Save Notify","NM","save_notify");
|
| 61 | + }
|
| 62 | + /*ENDLOG*/
|
| 63 | +
|
| 64 | + $('shade').toggle();
|
| 65 | + if(this.pendingElement)
|
| 66 | + this.pendingElement.hide();
|
| 67 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 68 | + this.pendingElement.show();
|
| 69 | +
|
| 70 | + if($('nmqname').value=="") {
|
| 71 | + var request = Array();
|
| 72 | + request.responseText = nmLanguage.getMessage('NM_EMPTY_NOTIFYNAME');
|
| 73 | + this.saveNotify(request);
|
| 74 | + }
|
| 75 | + else if ($('nmquery').value){ //only do this if the query is not empty
|
| 76 | + var params = $('nmquery').value.replace(/&/gm, "&").replace(/,/gm, ",") + ",";
|
| 77 | + params += ($('nmqrall').checked ? 1 : 0) + ",";
|
| 78 | + params += ($('nmqsall').checked ? 1 : 0) + ",";
|
| 79 | + params += $('nmqname').value.replace(/&/gm, "&").replace(/,/gm, ",") + ",";
|
| 80 | + params += ($('nmd_new') ? $('nmd_new').value : "");
|
| 81 | + sajax_do_call('smwf_nm_NotifyAccess', ["addNotify", params], this.saveNotify.bind(this));
|
| 82 | + }
|
| 83 | + else { // query is empty
|
| 84 | + var request = Array();
|
| 85 | + request.responseText = nmLanguage.getMessage('NM_EMPTY_QUERY');
|
| 86 | + this.saveNotify(request);
|
| 87 | + }
|
| 88 | +},
|
| 89 | +
|
| 90 | +/**
|
| 91 | +* Displays the preview created by the server
|
| 92 | +* @param request Request of AJAX call
|
| 93 | +*/
|
| 94 | +saveNotify:function(request){
|
| 95 | + this.pendingElement.hide();
|
| 96 | + var s = request.responseText;
|
| 97 | + if(s.substring(0,1)=="1") {
|
| 98 | + var enabled = (s.substring(1,2)=='1');
|
| 99 | + s = s.substring(2);
|
| 100 | + var i = s.indexOf(",");
|
| 101 | + var nid = s.substring(0,i);
|
| 102 | + this.addNotifyToTable(nid, enabled);
|
| 103 | + alert("Notify create successfully!" + s.substring(i+1));
|
| 104 | + } else alert(s);
|
| 105 | + $('shade').toggle();
|
| 106 | +},
|
| 107 | +addNotifyToTable:function(nid, enabled){
|
| 108 | + var ntr = document.createElement("tr");
|
| 109 | + var td = document.createElement("td");
|
| 110 | + ntr.appendChild(td);
|
| 111 | + var item = document.createElement("input");
|
| 112 | + td.appendChild(item);
|
| 113 | + item.type = "checkbox";
|
| 114 | + item.value = nid;
|
| 115 | + item.name = "nmdel";
|
| 116 | +
|
| 117 | + td = document.createElement("td");
|
| 118 | + ntr.appendChild(td);
|
| 119 | + item = document.createElement("a");
|
| 120 | + td.appendChild(item);
|
| 121 | + item.href = "index.php?title=Special:NotifyMe&feed=rss&nid=" + nid;
|
| 122 | + item.target = "_blank";
|
| 123 | + item.innerHTML = $('nmqname').value;
|
| 124 | +
|
| 125 | + td = document.createElement("td");
|
| 126 | + ntr.appendChild(td);
|
| 127 | + td.innerHTML = $('nmquery').value.replace(/\n/g, "<br/>");
|
| 128 | +
|
| 129 | + td = document.createElement("td");
|
| 130 | + ntr.appendChild(td);
|
| 131 | + item = document.createElement("input");
|
| 132 | + td.appendChild(item);
|
| 133 | + item.type = "checkbox";
|
| 134 | + item.value = nid;
|
| 135 | + item.name = "nmall";
|
| 136 | + item.checked = $('nmqrall').checked;
|
| 137 | +
|
| 138 | + td = document.createElement("td");
|
| 139 | + ntr.appendChild(td);
|
| 140 | + item = document.createElement("input");
|
| 141 | + td.appendChild(item);
|
| 142 | + item.type = "checkbox";
|
| 143 | + item.value = nid;
|
| 144 | + item.name = "nmsall";
|
| 145 | + item.checked = $('nmqsall').checked;
|
| 146 | +
|
| 147 | + td = document.createElement("td");
|
| 148 | + ntr.appendChild(td);
|
| 149 | + item = document.createElement("input");
|
| 150 | + td.appendChild(item);
|
| 151 | + item.type = "checkbox";
|
| 152 | + item.value = nid;
|
| 153 | + item.name = "nmenable";
|
| 154 | + item.id = "nmenable_" + nid;
|
| 155 | + item.checked = enabled;
|
| 156 | +
|
| 157 | + if($('nmd_new')) {
|
| 158 | + td = document.createElement("td");
|
| 159 | + ntr.appendChild(td);
|
| 160 | + item = document.createElement("input");
|
| 161 | + td.appendChild(item);
|
| 162 | + item.type = "text";
|
| 163 | + item.value = $('nmd_new').value;
|
| 164 | + item.name = "nmdelegate";
|
| 165 | + item.id = "nmd_" + nid;
|
| 166 | +
|
| 167 | + item = document.createElement("div");
|
| 168 | + td.appendChild(item);
|
| 169 | + item.class = "page_name_auto_complete";
|
| 170 | + item.id = "nmdiv_" + nid;
|
| 171 | + }
|
| 172 | +
|
| 173 | + $('nmtoolbar').parentNode.insertBefore(ntr, $('nmtoolbar'));
|
| 174 | + attachAutocompleteToField("nmd_" + nid);
|
| 175 | +},
|
| 176 | +
|
| 177 | +doUpdateMail:function() {
|
| 178 | + /*STARTLOG*/
|
| 179 | + if(window.smwhgLogger){
|
| 180 | + smwhgLogger.log("update Notify Me mail setting ","NM","update_mail");
|
| 181 | + }
|
| 182 | + /*ENDLOG*/
|
| 183 | +
|
| 184 | + $('shade').toggle();
|
| 185 | + if(this.pendingElement)
|
| 186 | + this.pendingElement.hide();
|
| 187 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 188 | + this.pendingElement.show();
|
| 189 | +
|
| 190 | + var params = ($('nmemail').checked ? 1 : 0);
|
| 191 | + sajax_do_call('smwf_nm_NotifyAccess', ["updateMail", params], this.updateMail.bind(this));
|
| 192 | +},
|
| 193 | +updateMail:function(request){
|
| 194 | + this.pendingElement.hide();
|
| 195 | + alert(request.responseText);
|
| 196 | + $('shade').toggle();
|
| 197 | +},
|
| 198 | +
|
| 199 | +resetQuery:function(){
|
| 200 | + $('nmquery').value = "";
|
| 201 | + $('nmqname').value = "";
|
| 202 | + $('nmd_new').value = "";
|
| 203 | + $('nmqrall').checked = true;
|
| 204 | + $('nmqsall').checked = false;
|
| 205 | +},
|
| 206 | +
|
| 207 | +/**
|
| 208 | +* Gets all display parameters and the full ask syntax to perform an ajax call
|
| 209 | +* which will create the preview
|
| 210 | +*/
|
| 211 | +previewQuery:function(){
|
| 212 | +
|
| 213 | + /*STARTLOG*/
|
| 214 | + if(window.smwhgLogger){
|
| 215 | + smwhgLogger.log("Preview Query","NM","query_preview");
|
| 216 | + }
|
| 217 | + /*ENDLOG*/
|
| 218 | + $('shade').toggle();
|
| 219 | + if(this.pendingElement)
|
| 220 | + this.pendingElement.hide();
|
| 221 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 222 | + this.pendingElement.show();
|
| 223 | + if ($('nmquery').value){ //only do this if the query is not empty
|
| 224 | + sajax_do_call('smwf_nm_NotifyAccess', ["getQueryResult", $('nmquery').value], this.openPreview.bind(this));
|
| 225 | + }
|
| 226 | + else { // query is empty
|
| 227 | + var request = Array();
|
| 228 | + request.responseText = nmLanguage.getMessage('NM_EMPTY_QUERY');
|
| 229 | + this.openPreview(request);
|
| 230 | + }
|
| 231 | +},
|
| 232 | +/**
|
| 233 | +* Displays the preview created by the server
|
| 234 | +* @param request Request of AJAX call
|
| 235 | +*/
|
| 236 | +openPreview:function(request){
|
| 237 | + this.pendingElement.hide();
|
| 238 | + $('fullpreviewbox').toggle();
|
| 239 | + $('fullpreview').innerHTML = request.responseText;
|
| 240 | +},
|
| 241 | +
|
| 242 | +
|
| 243 | +updateStates:function(){
|
| 244 | + /*STARTLOG*/
|
| 245 | + if(window.smwhgLogger){
|
| 246 | + smwhgLogger.log("Update States","NM","update_states");
|
| 247 | + }
|
| 248 | + /*ENDLOG*/
|
| 249 | +
|
| 250 | + $('shade').toggle();
|
| 251 | + if(this.pendingElement)
|
| 252 | + this.pendingElement.hide();
|
| 253 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 254 | + this.pendingElement.show();
|
| 255 | +
|
| 256 | + var params = "";
|
| 257 | + var nodes = $A(document.getElementsByName('nmenable'));
|
| 258 | + var dNodes = nodes.select(function(node) {
|
| 259 | + return node.checked;
|
| 260 | + });
|
| 261 | + dNodes.each(function(node) {
|
| 262 | + params += node.value + ',';
|
| 263 | + });
|
| 264 | + sajax_do_call('smwf_nm_NotifyAccess', ["updateStates", params], this.updateStatesDone.bind(this));
|
| 265 | +},
|
| 266 | +/**
|
| 267 | +* @param request Request of AJAX call
|
| 268 | +*/
|
| 269 | +updateStatesDone:function(request){
|
| 270 | + this.pendingElement.hide();
|
| 271 | + var s = request.responseText;
|
| 272 | + if(s.substring(0,1)=="0") {
|
| 273 | + s = s.substring(1);
|
| 274 | + var i = s.indexOf("|");
|
| 275 | + var nids = s.substring(0,i).split(",");
|
| 276 | + for(j=nids.length-2;j>=0;--j) {
|
| 277 | + $('nmenable_'+nids[j]).checked = false;
|
| 278 | + }
|
| 279 | + alert("Notify create failed!" + s.substring(i+1));
|
| 280 | + } else alert(s);
|
| 281 | + $('shade').toggle();
|
| 282 | +},
|
| 283 | +
|
| 284 | +updateReportAll:function(){
|
| 285 | + /*STARTLOG*/
|
| 286 | + if(window.smwhgLogger){
|
| 287 | + smwhgLogger.log("Update Notifications report all","NM","update_reportall");
|
| 288 | + }
|
| 289 | + /*ENDLOG*/
|
| 290 | +
|
| 291 | + $('shade').toggle();
|
| 292 | + if(this.pendingElement)
|
| 293 | + this.pendingElement.hide();
|
| 294 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 295 | + this.pendingElement.show();
|
| 296 | +
|
| 297 | + var params = "";
|
| 298 | + var nodes = $A(document.getElementsByName('nmall'));
|
| 299 | + var dNodes = nodes.select(function(node) {
|
| 300 | + return node.checked;
|
| 301 | + });
|
| 302 | + dNodes.each(function(node) {
|
| 303 | + params += node.value + ',';
|
| 304 | + });
|
| 305 | + sajax_do_call('smwf_nm_NotifyAccess', ["updateReportAll", params], this.updateDone.bind(this));
|
| 306 | +},
|
| 307 | +
|
| 308 | +updateShowAll:function(){
|
| 309 | + /*STARTLOG*/
|
| 310 | + if(window.smwhgLogger){
|
| 311 | + smwhgLogger.log("Update Notifications show all","NM","update_showall");
|
| 312 | + }
|
| 313 | + /*ENDLOG*/
|
| 314 | +
|
| 315 | + $('shade').toggle();
|
| 316 | + if(this.pendingElement)
|
| 317 | + this.pendingElement.hide();
|
| 318 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 319 | + this.pendingElement.show();
|
| 320 | +
|
| 321 | + var params = "";
|
| 322 | + var nodes = $A(document.getElementsByName('nmsall'));
|
| 323 | + var dNodes = nodes.select(function(node) {
|
| 324 | + return node.checked;
|
| 325 | + });
|
| 326 | + dNodes.each(function(node) {
|
| 327 | + params += node.value + ',';
|
| 328 | + });
|
| 329 | + sajax_do_call('smwf_nm_NotifyAccess', ["updateShowAll", params], this.updateDone.bind(this));
|
| 330 | +},
|
| 331 | +/**
|
| 332 | +* @param request Request of AJAX call
|
| 333 | +*/
|
| 334 | +updateDone:function(request){
|
| 335 | + this.pendingElement.hide();
|
| 336 | + alert(request.responseText);
|
| 337 | + $('shade').toggle();
|
| 338 | +},
|
| 339 | +
|
| 340 | +deleteNotify:function(){
|
| 341 | + /*STARTLOG*/
|
| 342 | + if(window.smwhgLogger){
|
| 343 | + smwhgLogger.log("Delete Notifications","NM","delete_notify");
|
| 344 | + }
|
| 345 | + /*ENDLOG*/
|
| 346 | +
|
| 347 | + $('shade').toggle();
|
| 348 | + if(this.pendingElement)
|
| 349 | + this.pendingElement.hide();
|
| 350 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 351 | + this.pendingElement.show();
|
| 352 | +
|
| 353 | + var params = "";
|
| 354 | + var nodes = $A(document.getElementsByName('nmdel'));
|
| 355 | + var dNodes = nodes.select(function(node) {
|
| 356 | + return node.checked;
|
| 357 | + });
|
| 358 | + dNodes.each(function(node) {
|
| 359 | + params += node.value + ',';
|
| 360 | + });
|
| 361 | + sajax_do_call('smwf_nm_NotifyAccess', ["delNotify", params], this.delDone.bind(this));
|
| 362 | +},
|
| 363 | +/**
|
| 364 | +* @param request Request of AJAX call
|
| 365 | +*/
|
| 366 | +delDone:function(request){
|
| 367 | + this.pendingElement.hide();
|
| 368 | + var nodes = $A(document.getElementsByName('nmdel'));
|
| 369 | + var sid='';
|
| 370 | + var idx=1;
|
| 371 | + nodes.each(function(node) {
|
| 372 | + if(node.checked) sid += idx+',';
|
| 373 | + idx++;
|
| 374 | + return node.checked;
|
| 375 | + });
|
| 376 | + var ids = sid.split(',');
|
| 377 | + for(i=ids.length-2;i>=0;--i){
|
| 378 | + $('nmtable').deleteRow(ids[i]);
|
| 379 | + }
|
| 380 | + alert(request.responseText);
|
| 381 | + $('shade').toggle();
|
| 382 | +},
|
| 383 | +
|
| 384 | +delall:function(chked){
|
| 385 | + var nodes = $A(document.getElementsByName('nmdel'));
|
| 386 | + nodes.each(function(node) {
|
| 387 | + node.checked = chked;
|
| 388 | + });
|
| 389 | +},
|
| 390 | +enableall:function(chked){
|
| 391 | + var nodes = $A(document.getElementsByName('nmenable'));
|
| 392 | + nodes.each(function(node) {
|
| 393 | + node.checked = chked;
|
| 394 | + });
|
| 395 | +},
|
| 396 | +reportall:function(chked){
|
| 397 | + var nodes = $A(document.getElementsByName('nmall'));
|
| 398 | + nodes.each(function(node) {
|
| 399 | + node.checked = chked;
|
| 400 | + });
|
| 401 | +},
|
| 402 | +showall:function(chked){
|
| 403 | + var nodes = $A(document.getElementsByName('nmsall'));
|
| 404 | + nodes.each(function(node) {
|
| 405 | + node.checked = chked;
|
| 406 | + });
|
| 407 | +},
|
| 408 | +resetNotify:function(){
|
| 409 | + this.delall(false);
|
| 410 | + this.enableall(true);
|
| 411 | +},
|
| 412 | +updateDelegate:function(){
|
| 413 | + /*STARTLOG*/
|
| 414 | + if(window.smwhgLogger){
|
| 415 | + smwhgLogger.log("Update delegates","NM","update_delegates");
|
| 416 | + }
|
| 417 | + /*ENDLOG*/
|
| 418 | +
|
| 419 | + $('shade').toggle();
|
| 420 | + if(this.pendingElement)
|
| 421 | + this.pendingElement.hide();
|
| 422 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 423 | + this.pendingElement.show();
|
| 424 | +
|
| 425 | + var params = "";
|
| 426 | + var nodes = $A(document.getElementsByName('nmdelegate'));
|
| 427 | + var dNodes = nodes.select(function(node) {
|
| 428 | + return (node.value != '');
|
| 429 | + });
|
| 430 | + dNodes.each(function(node) {
|
| 431 | + params += node.id.substring(4) + ':' + node.value + '|';
|
| 432 | + });
|
| 433 | + sajax_do_call('smwf_nm_NotifyAccess', ["updateDelegates", params], this.updateDone.bind(this));
|
| 434 | +},
|
| 435 | +copyToClipboard:function(id){
|
| 436 | + /*STARTLOG*/
|
| 437 | + if(window.smwhgLogger){
|
| 438 | + smwhgLogger.log("Copy nm rss to clipboard","NM","rss_copied");
|
| 439 | + }
|
| 440 | + /*ENDLOG*/
|
| 441 | + var text = $(id).value;
|
| 442 | + var succ = 'The RSS feed url was successfully copied to your clipboard';
|
| 443 | + var fail = 'Your browser does not allow clipboard access.\nThe RSS feed url could not be copied to your clipboard.\nPlease copy the RSS feed url manually.';
|
| 444 | + if (window.clipboardData){ //IE
|
| 445 | + window.clipboardData.setData("Text", text);
|
| 446 | + alert(succ);
|
| 447 | + }
|
| 448 | + else if (window.netscape) {
|
| 449 | + try {
|
| 450 | + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
| 451 | + var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(Components.interfaces.nsIClipboard);
|
| 452 | + if (!clip){
|
| 453 | + alert(fail);
|
| 454 | + return;
|
| 455 | + }
|
| 456 | + var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable);
|
| 457 | + if (!trans){
|
| 458 | + alert(fail);
|
| 459 | + return;
|
| 460 | + }
|
| 461 | + trans.addDataFlavor('text/unicode');
|
| 462 | + var str = new Object();
|
| 463 | + var len = new Object();
|
| 464 | + var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
|
| 465 | + str.data=text;
|
| 466 | + trans.setTransferData("text/unicode",str,text.length*2);
|
| 467 | + var clipid=Components.interfaces.nsIClipboard;
|
| 468 | + if (!clip){
|
| 469 | + alert(fail);
|
| 470 | + return;
|
| 471 | + }
|
| 472 | + clip.setData(trans,null,clipid.kGlobalClipboard);
|
| 473 | + alert(succ);
|
| 474 | + }
|
| 475 | + catch (e) {
|
| 476 | + alert(fail);
|
| 477 | + }
|
| 478 | + }
|
| 479 | + else{
|
| 480 | + alert(fail);
|
| 481 | + }
|
| 482 | +},
|
| 483 | +
|
| 484 | +// this function is highly depend on SpecialPage : QueryInterface
|
| 485 | +saveQueryToNotify:function() {
|
| 486 | + if(!$('addnotifydialogue')) {
|
| 487 | + var askdlg = $('showAsk');
|
| 488 | + var nmdlg = document.createElement("div");
|
| 489 | + askdlg.parentNode.insertBefore(nmdlg, askdlg);
|
| 490 | + nmdlg.id = "addnotifydialogue";
|
| 491 | + nmdlg.className = "topDialogue";
|
| 492 | + nmdlg.style.display = "none";
|
| 493 | + var html = "Do you really want to receive any notification of article-updates followed after your query?<br/>";
|
| 494 | + html += "Name of the notification: <input type=\"text\" id=\"nmqname\"/><br />";
|
| 495 | + html += "<input type=\"checkbox\" id=\"nmqrall\" checked> Notify me all semantic attributes\' change of monitored pages (Report All)<br />";
|
| 496 | + html += "<input type=\"checkbox\" id=\"nmqsall\"> Show all query results with notification (Show All)<br />";
|
| 497 | + html += "<span class=\"qibutton\" onclick=\"notifyhelper.doSaveToNotifyQI()\">OK</span> <span class=\"qibutton\" onclick=\"notifyhelper.doCancelNotifyQI()\">Cancel</span>";
|
| 498 | + nmdlg.innerHTML = html;
|
| 499 | + }
|
| 500 | + $('shade').toggle();
|
| 501 | + $('addnotifydialogue').toggle();
|
| 502 | +},
|
| 503 | +doSaveToNotifyQI:function(){
|
| 504 | + if(window.smwhgLogger){
|
| 505 | + smwhgLogger.log("Save Notify","QI","save_notify");
|
| 506 | + }
|
| 507 | + if(this.pendingElement)
|
| 508 | + this.pendingElement.hide();
|
| 509 | + this.pendingElement = new OBPendingIndicator($('shade'));
|
| 510 | + this.pendingElement.show();
|
| 511 | +
|
| 512 | + if($('nmqname').value=="") {
|
| 513 | + alert(nmLanguage.getMessage('NM_EMPTY_NOTIFYNAME'));
|
| 514 | + }
|
| 515 | + else if (!qihelper.queries[0].isEmpty()){ //only do this if the query is not empty
|
| 516 | + var ask = qihelper.recurseQuery(0, "parser"); // Get full ask syntax
|
| 517 | + qihelper.queries[0].getDisplayStatements().each(function(s) { ask += "\n| ?" + s});
|
| 518 | + if($('layout_intro').value!="") ask += "\n| intro=" + $('layout_intro').value;
|
| 519 | + if($('layout_sort').value!=gLanguage.getMessage('QI_ARTICLE_TITLE')) ask += "\n| sort=" + $('layout_sort').value;
|
| 520 | + if($('layout_limit').value!="") ask += "\n| limit=" + $('layout_limit').value;
|
| 521 | + if($('layout_label').value!="") ask += "\n| mainlabel=" + $('layout_label').value;
|
| 522 | + if($('layout_order').value!="ascending") ask += "\n| order=descending";
|
| 523 | + if($('layout_default').value!="") ask += "\n| default=" + $('layout_default').value;
|
| 524 | + if(!$('layout_headers').checked) ask += "\n| headers=hide";
|
| 525 | +
|
| 526 | + var params = ask.replace(/&/gm, "&").replace(/,/gm, ",") + ",";
|
| 527 | + params += ($('nmqrall').checked ? 1 : 0) + ",";
|
| 528 | + params += ($('nmqsall').checked ? 1 : 0) + ",";
|
| 529 | + params += $('nmqname').value.replace(/&/gm, "&").replace(/,/gm, ",") + ",";
|
| 530 | + params += ($('nmd_new') ? $('nmd_new').value : "");
|
| 531 | + sajax_do_call('smwf_nm_NotifyAccess', ["addNotify", params], this.saveNotifyQI.bind(this));
|
| 532 | + }
|
| 533 | + else { // query is empty
|
| 534 | + alert(gLanguage.getMessage('QI_EMPTY_QUERY'));
|
| 535 | + }
|
| 536 | +},
|
| 537 | +saveNotifyQI:function(request){
|
| 538 | + this.pendingElement.hide();
|
| 539 | + var s = request.responseText;
|
| 540 | + if(s.substring(0,1)=="1") {
|
| 541 | + var enabled = (s.substring(1,2)=='1');
|
| 542 | + s = s.substring(2);
|
| 543 | + var i = s.indexOf(",");
|
| 544 | + var nid = s.substring(0,i);
|
| 545 | + alert("Notify create successfully!" + s.substring(i+1));
|
| 546 | + } else alert(s);
|
| 547 | + $('addnotifydialogue').toggle();
|
| 548 | + $('shade').toggle();
|
| 549 | +},
|
| 550 | +doCancelNotifyQI:function(){
|
| 551 | + $('addnotifydialogue').toggle();
|
| 552 | + $('shade').toggle();
|
| 553 | +}
|
| 554 | +
|
| 555 | +} //end class notifyHelper
|
| 556 | +
|
| 557 | +Event.observe(window,'load',initialize_notify);
|
| 558 | +
|
| 559 | +function initialize_notify(){
|
| 560 | + notifyhelper = new NotifyHelper();
|
| 561 | +
|
| 562 | + // SMW / Halo extension contains wibbit which hooks all checkboxes,
|
| 563 | + // have to load the event handler afterwards
|
| 564 | + if($('nmemail')) $('nmemail').onclick = function() {notifyhelper.doUpdateMail();};
|
| 565 | +}
|
Index: trunk/extensions/SemanticNotifyMe/skins/nm.css |
— | — | @@ -0,0 +1,230 @@ |
| 2 | +#qicontent{
|
| 3 | +cursor: default;
|
| 4 | +font-family: sans-serif;
|
| 5 | +font-size-adjust:none;
|
| 6 | +font-style:normal;
|
| 7 | +font-variant:normal;
|
| 8 | +max-width:100%;
|
| 9 | +width:100%;
|
| 10 | +height:550px;
|
| 11 | +}
|
| 12 | +
|
| 13 | +#shade{
|
| 14 | +position: absolute;
|
| 15 | +top: 0px;
|
| 16 | +left: 0px;
|
| 17 | +background:#444444;
|
| 18 | +-moz-opacity: 0.2;
|
| 19 | +filter:alpha(opacity=20);
|
| 20 | +opacity: 0.2;
|
| 21 | +-khtml-opacity: 0.2;
|
| 22 | +width:100%;
|
| 23 | +height:100%;
|
| 24 | +z-index: 50;
|
| 25 | +}
|
| 26 | +
|
| 27 | +.nmmenubar{
|
| 28 | +border: 1px solid #C0C0C0;
|
| 29 | +background: #DDDDDD;
|
| 30 | +width:100%;
|
| 31 | +margin-top:10px;
|
| 32 | +margin-bottom:10px;
|
| 33 | +}
|
| 34 | +
|
| 35 | +.nmbutton{
|
| 36 | +font-family: sans-serif;
|
| 37 | +text-decoration: underline;
|
| 38 | +color: #0000FF;
|
| 39 | +cursor: pointer;
|
| 40 | +padding-right: 5px;
|
| 41 | +padding-left: 5px;
|
| 42 | +}
|
| 43 | +
|
| 44 | +.dragbox{
|
| 45 | +float:left;
|
| 46 | +width:73%;
|
| 47 | +max-width:73%;
|
| 48 | +}
|
| 49 | +
|
| 50 | +.boxcontent {
|
| 51 | +border: 1px solid #AAAAAA;
|
| 52 | +min-width: 70%;
|
| 53 | +width:100%;
|
| 54 | +height: 209px;
|
| 55 | +float:left;
|
| 56 | +overflow:auto;
|
| 57 | +}
|
| 58 | +
|
| 59 | +.boxcontent img{
|
| 60 | +cursor:pointer;
|
| 61 | +}
|
| 62 | +
|
| 63 | +.helpcontent {
|
| 64 | +border: 1px solid #AAAAAA;
|
| 65 | +min-width: 28%;
|
| 66 | +width:28%;
|
| 67 | +height: 209px;
|
| 68 | +float:left;
|
| 69 | +overflow:auto;
|
| 70 | +margin-left:5px;
|
| 71 | +background-color: #FFFFCC;
|
| 72 | +border: 1px solid #E6E600;
|
| 73 | +}
|
| 74 | +
|
| 75 | +#querylayout{
|
| 76 | +width:100%;
|
| 77 | +border: 1px solid #AAAAAA;
|
| 78 | +float:left;
|
| 79 | +margin-top: 10px;
|
| 80 | +}
|
| 81 | +
|
| 82 | +#layouttitle{
|
| 83 | +background: #DDDDDD;
|
| 84 | +cursor:pointer;
|
| 85 | +font-weight: bold;
|
| 86 | +margin-top:10px;
|
| 87 | +}
|
| 88 | +
|
| 89 | +#layoutcontent{
|
| 90 | +border-top:1px solid #AAAAAA;
|
| 91 | +background: #FFFFFF;
|
| 92 | +}
|
| 93 | +
|
| 94 | +#querytitle{
|
| 95 | +background: #DDDDDD;
|
| 96 | +cursor:pointer;
|
| 97 | +font-weight: bold;
|
| 98 | +}
|
| 99 | +
|
| 100 | +#querycontent{
|
| 101 | +border-top:1px solid #AAAAAA;
|
| 102 | +background: #FFFFFF;
|
| 103 | +}
|
| 104 | +
|
| 105 | +#querycontent select{
|
| 106 | +/*width:95%;*/
|
| 107 | +}
|
| 108 | +
|
| 109 | +#previewlayout{
|
| 110 | +width:100%;
|
| 111 | +border: 1px solid #AAAAAA;
|
| 112 | +float:left;
|
| 113 | +margin-top: 10px;
|
| 114 | +}
|
| 115 | +
|
| 116 | +#previewtitle{
|
| 117 | +background: #DDDDDD;
|
| 118 | +cursor:pointer;
|
| 119 | +font-weight: bold;
|
| 120 | +}
|
| 121 | +
|
| 122 | +#previewcontent{
|
| 123 | +border-top:1px solid #AAAAAA;
|
| 124 | +background: #FFFFFF;
|
| 125 | +}
|
| 126 | +
|
| 127 | +
|
| 128 | +.selectbox{
|
| 129 | +float:left;
|
| 130 | +border: 1px solid #AAAAAA;
|
| 131 | +margin-left:10px;
|
| 132 | +}
|
| 133 | +.selectboxheader{
|
| 134 | +font-weight: bold;
|
| 135 | +color: #000000;
|
| 136 | +background: #DDDDDD;
|
| 137 | +border-bottom: 1px solid #AAAAAA;
|
| 138 | +}
|
| 139 | +
|
| 140 | +.selectboxcontent{
|
| 141 | +background:#FFFFFF;
|
| 142 | +padding:5px;
|
| 143 | +}
|
| 144 | +
|
| 145 | +a.minusplus{
|
| 146 | + background-image:url(images/minus-pas.gif);
|
| 147 | + float:left;
|
| 148 | + height:12px;
|
| 149 | + margin-right:4px;
|
| 150 | + margin-top:3px;
|
| 151 | + overflow:hidden;
|
| 152 | + width:12px;
|
| 153 | +}
|
| 154 | +a.minusplus:hover{
|
| 155 | + background-image:url(images/minus-act.gif);
|
| 156 | +}
|
| 157 | +
|
| 158 | +a.plusminus{
|
| 159 | + background-image:url(images/plus-pas.gif);
|
| 160 | + float:left;
|
| 161 | + height:12px;
|
| 162 | + margin-right:4px;
|
| 163 | + margin-top:3px;
|
| 164 | + overflow:hidden;
|
| 165 | + width:12px;
|
| 166 | +}
|
| 167 | +a.plusminus:hover{
|
| 168 | + background-image:url(images/plus-act.gif);
|
| 169 | +}
|
| 170 | +
|
| 171 | +#fullpreviewbox{
|
| 172 | +position:absolute;
|
| 173 | +z-index:51;
|
| 174 | +top:10%;
|
| 175 | +left:10%;
|
| 176 | +background:#FFF;
|
| 177 | +border: 1px solid #AAAAAA;
|
| 178 | +padding:10px;
|
| 179 | +}
|
| 180 | +
|
| 181 | +#fullpreview{
|
| 182 | +padding:5px;
|
| 183 | +max-height:200px;
|
| 184 | +overflow:auto;
|
| 185 | +}
|
| 186 | +
|
| 187 | +.subquerycell{
|
| 188 | +border-left:1px solid;
|
| 189 | +border-color: #AAAAAA;
|
| 190 | +}
|
| 191 | +
|
| 192 | +.topDialogue{
|
| 193 | +position:absolute;
|
| 194 | +z-index:51;
|
| 195 | +top:25%;
|
| 196 | +left:30%;
|
| 197 | +background:#FFF;
|
| 198 | +border: 1px solid #AAAAAA;
|
| 199 | +padding:5px;
|
| 200 | +}
|
| 201 | +
|
| 202 | +#dialoguecontent tr td{
|
| 203 | +text-align:right;
|
| 204 | +}
|
| 205 | +
|
| 206 | +#dialoguecontent tr td td{
|
| 207 | +text-align:left;
|
| 208 | +}
|
| 209 | +
|
| 210 | +button.btn{
|
| 211 | +color:#000;
|
| 212 | +font-family:'trebuchet ms',helvetica,sans-serif;
|
| 213 | +font-size:84%;
|
| 214 | +font-weight:bold;
|
| 215 | +background-color:#eee;
|
| 216 | +border:1px solid;
|
| 217 | +border-top-color:#c0c0c0;
|
| 218 | +border-left-color:#c0c0c0;
|
| 219 | +border-right-color:#606060;
|
| 220 | +border-bottom-color:#606060;
|
| 221 | +margin:2px;
|
| 222 | +cursor: pointer;
|
| 223 | +}
|
| 224 | +button.btnhov{
|
| 225 | +border-top-color:#00f;
|
| 226 | +border-left-color:#00f;
|
| 227 | +border-right-color:#00f;
|
| 228 | +border-bottom-color:#00f;
|
| 229 | +cursor: pointer;
|
| 230 | +}
|
| 231 | +
|
Index: trunk/extensions/SemanticNotifyMe/skins/images/minus-pas.gif |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/SemanticNotifyMe/skins/images/minus-pas.gif |
___________________________________________________________________ |
Name: svn:mime-type |
1 | 232 | + application/octet-stream |
Index: trunk/extensions/SemanticNotifyMe/skins/images/minus-act.gif |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/SemanticNotifyMe/skins/images/minus-act.gif |
___________________________________________________________________ |
Name: svn:mime-type |
2 | 233 | + application/octet-stream |
Index: trunk/extensions/SemanticNotifyMe/skins/images/delete.png |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/SemanticNotifyMe/skins/images/delete.png |
___________________________________________________________________ |
Name: svn:mime-type |
3 | 234 | + application/octet-stream |
Index: trunk/extensions/SemanticNotifyMe/skins/images/plus-pas.gif |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/SemanticNotifyMe/skins/images/plus-pas.gif |
___________________________________________________________________ |
Name: svn:mime-type |
4 | 235 | + application/octet-stream |
Index: trunk/extensions/SemanticNotifyMe/skins/images/plus-act.gif |
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes on: trunk/extensions/SemanticNotifyMe/skins/images/plus-act.gif |
___________________________________________________________________ |
Name: svn:mime-type |
5 | 236 | + application/octet-stream |
Index: trunk/extensions/SemanticNotifyMe/COPYING |
— | — | @@ -0,0 +1,348 @@ |
| 2 | +The license text below "----" applies to all files within this distribution, other
|
| 3 | +than those that are in a directory which contains files named "LICENSE" or
|
| 4 | +"COPYING", or a subdirectory thereof. For those files, the license text contained in
|
| 5 | +said file overrides any license information contained in directories of smaller depth.
|
| 6 | +Alternative licenses are typically used for software that is provided by external
|
| 7 | +parties, and merely packaged with the Semantic MediaWiki release for convenience.
|
| 8 | +----
|
| 9 | +
|
| 10 | + GNU GENERAL PUBLIC LICENSE
|
| 11 | + Version 2, June 1991
|
| 12 | +
|
| 13 | + Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
| 14 | + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
| 15 | + Everyone is permitted to copy and distribute verbatim copies
|
| 16 | + of this license document, but changing it is not allowed.
|
| 17 | +
|
| 18 | + Preamble
|
| 19 | +
|
| 20 | + The licenses for most software are designed to take away your
|
| 21 | +freedom to share and change it. By contrast, the GNU General Public
|
| 22 | +License is intended to guarantee your freedom to share and change free
|
| 23 | +software--to make sure the software is free for all its users. This
|
| 24 | +General Public License applies to most of the Free Software
|
| 25 | +Foundation's software and to any other program whose authors commit to
|
| 26 | +using it. (Some other Free Software Foundation software is covered by
|
| 27 | +the GNU Library General Public License instead.) You can apply it to
|
| 28 | +your programs, too.
|
| 29 | +
|
| 30 | + When we speak of free software, we are referring to freedom, not
|
| 31 | +price. Our General Public Licenses are designed to make sure that you
|
| 32 | +have the freedom to distribute copies of free software (and charge for
|
| 33 | +this service if you wish), that you receive source code or can get it
|
| 34 | +if you want it, that you can change the software or use pieces of it
|
| 35 | +in new free programs; and that you know you can do these things.
|
| 36 | +
|
| 37 | + To protect your rights, we need to make restrictions that forbid
|
| 38 | +anyone to deny you these rights or to ask you to surrender the rights.
|
| 39 | +These restrictions translate to certain responsibilities for you if you
|
| 40 | +distribute copies of the software, or if you modify it.
|
| 41 | +
|
| 42 | + For example, if you distribute copies of such a program, whether
|
| 43 | +gratis or for a fee, you must give the recipients all the rights that
|
| 44 | +you have. You must make sure that they, too, receive or can get the
|
| 45 | +source code. And you must show them these terms so they know their
|
| 46 | +rights.
|
| 47 | +
|
| 48 | + We protect your rights with two steps: (1) copyright the software, and
|
| 49 | +(2) offer you this license which gives you legal permission to copy,
|
| 50 | +distribute and/or modify the software.
|
| 51 | +
|
| 52 | + Also, for each author's protection and ours, we want to make certain
|
| 53 | +that everyone understands that there is no warranty for this free
|
| 54 | +software. If the software is modified by someone else and passed on, we
|
| 55 | +want its recipients to know that what they have is not the original, so
|
| 56 | +that any problems introduced by others will not reflect on the original
|
| 57 | +authors' reputations.
|
| 58 | +
|
| 59 | + Finally, any free program is threatened constantly by software
|
| 60 | +patents. We wish to avoid the danger that redistributors of a free
|
| 61 | +program will individually obtain patent licenses, in effect making the
|
| 62 | +program proprietary. To prevent this, we have made it clear that any
|
| 63 | +patent must be licensed for everyone's free use or not licensed at all.
|
| 64 | +
|
| 65 | + The precise terms and conditions for copying, distribution and
|
| 66 | +modification follow.
|
| 67 | +
|
| 68 | + GNU GENERAL PUBLIC LICENSE
|
| 69 | + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
| 70 | +
|
| 71 | + 0. This License applies to any program or other work which contains
|
| 72 | +a notice placed by the copyright holder saying it may be distributed
|
| 73 | +under the terms of this General Public License. The "Program", below,
|
| 74 | +refers to any such program or work, and a "work based on the Program"
|
| 75 | +means either the Program or any derivative work under copyright law:
|
| 76 | +that is to say, a work containing the Program or a portion of it,
|
| 77 | +either verbatim or with modifications and/or translated into another
|
| 78 | +language. (Hereinafter, translation is included without limitation in
|
| 79 | +the term "modification".) Each licensee is addressed as "you".
|
| 80 | +
|
| 81 | +Activities other than copying, distribution and modification are not
|
| 82 | +covered by this License; they are outside its scope. The act of
|
| 83 | +running the Program is not restricted, and the output from the Program
|
| 84 | +is covered only if its contents constitute a work based on the
|
| 85 | +Program (independent of having been made by running the Program).
|
| 86 | +Whether that is true depends on what the Program does.
|
| 87 | +
|
| 88 | + 1. You may copy and distribute verbatim copies of the Program's
|
| 89 | +source code as you receive it, in any medium, provided that you
|
| 90 | +conspicuously and appropriately publish on each copy an appropriate
|
| 91 | +copyright notice and disclaimer of warranty; keep intact all the
|
| 92 | +notices that refer to this License and to the absence of any warranty;
|
| 93 | +and give any other recipients of the Program a copy of this License
|
| 94 | +along with the Program.
|
| 95 | +
|
| 96 | +You may charge a fee for the physical act of transferring a copy, and
|
| 97 | +you may at your option offer warranty protection in exchange for a fee.
|
| 98 | +
|
| 99 | + 2. You may modify your copy or copies of the Program or any portion
|
| 100 | +of it, thus forming a work based on the Program, and copy and
|
| 101 | +distribute such modifications or work under the terms of Section 1
|
| 102 | +above, provided that you also meet all of these conditions:
|
| 103 | +
|
| 104 | + a) You must cause the modified files to carry prominent notices
|
| 105 | + stating that you changed the files and the date of any change.
|
| 106 | +
|
| 107 | + b) You must cause any work that you distribute or publish, that in
|
| 108 | + whole or in part contains or is derived from the Program or any
|
| 109 | + part thereof, to be licensed as a whole at no charge to all third
|
| 110 | + parties under the terms of this License.
|
| 111 | +
|
| 112 | + c) If the modified program normally reads commands interactively
|
| 113 | + when run, you must cause it, when started running for such
|
| 114 | + interactive use in the most ordinary way, to print or display an
|
| 115 | + announcement including an appropriate copyright notice and a
|
| 116 | + notice that there is no warranty (or else, saying that you provide
|
| 117 | + a warranty) and that users may redistribute the program under
|
| 118 | + these conditions, and telling the user how to view a copy of this
|
| 119 | + License. (Exception: if the Program itself is interactive but
|
| 120 | + does not normally print such an announcement, your work based on
|
| 121 | + the Program is not required to print an announcement.)
|
| 122 | +
|
| 123 | +These requirements apply to the modified work as a whole. If
|
| 124 | +identifiable sections of that work are not derived from the Program,
|
| 125 | +and can be reasonably considered independent and separate works in
|
| 126 | +themselves, then this License, and its terms, do not apply to those
|
| 127 | +sections when you distribute them as separate works. But when you
|
| 128 | +distribute the same sections as part of a whole which is a work based
|
| 129 | +on the Program, the distribution of the whole must be on the terms of
|
| 130 | +this License, whose permissions for other licensees extend to the
|
| 131 | +entire whole, and thus to each and every part regardless of who wrote it.
|
| 132 | +
|
| 133 | +Thus, it is not the intent of this section to claim rights or contest
|
| 134 | +your rights to work written entirely by you; rather, the intent is to
|
| 135 | +exercise the right to control the distribution of derivative or
|
| 136 | +collective works based on the Program.
|
| 137 | +
|
| 138 | +In addition, mere aggregation of another work not based on the Program
|
| 139 | +with the Program (or with a work based on the Program) on a volume of
|
| 140 | +a storage or distribution medium does not bring the other work under
|
| 141 | +the scope of this License.
|
| 142 | +
|
| 143 | + 3. You may copy and distribute the Program (or a work based on it,
|
| 144 | +under Section 2) in object code or executable form under the terms of
|
| 145 | +Sections 1 and 2 above provided that you also do one of the following:
|
| 146 | +
|
| 147 | + a) Accompany it with the complete corresponding machine-readable
|
| 148 | + source code, which must be distributed under the terms of Sections
|
| 149 | + 1 and 2 above on a medium customarily used for software interchange; or,
|
| 150 | +
|
| 151 | + b) Accompany it with a written offer, valid for at least three
|
| 152 | + years, to give any third party, for a charge no more than your
|
| 153 | + cost of physically performing source distribution, a complete
|
| 154 | + machine-readable copy of the corresponding source code, to be
|
| 155 | + distributed under the terms of Sections 1 and 2 above on a medium
|
| 156 | + customarily used for software interchange; or,
|
| 157 | +
|
| 158 | + c) Accompany it with the information you received as to the offer
|
| 159 | + to distribute corresponding source code. (This alternative is
|
| 160 | + allowed only for noncommercial distribution and only if you
|
| 161 | + received the program in object code or executable form with such
|
| 162 | + an offer, in accord with Subsection b above.)
|
| 163 | +
|
| 164 | +The source code for a work means the preferred form of the work for
|
| 165 | +making modifications to it. For an executable work, complete source
|
| 166 | +code means all the source code for all modules it contains, plus any
|
| 167 | +associated interface definition files, plus the scripts used to
|
| 168 | +control compilation and installation of the executable. However, as a
|
| 169 | +special exception, the source code distributed need not include
|
| 170 | +anything that is normally distributed (in either source or binary
|
| 171 | +form) with the major components (compiler, kernel, and so on) of the
|
| 172 | +operating system on which the executable runs, unless that component
|
| 173 | +itself accompanies the executable.
|
| 174 | +
|
| 175 | +If distribution of executable or object code is made by offering
|
| 176 | +access to copy from a designated place, then offering equivalent
|
| 177 | +access to copy the source code from the same place counts as
|
| 178 | +distribution of the source code, even though third parties are not
|
| 179 | +compelled to copy the source along with the object code.
|
| 180 | +
|
| 181 | + 4. You may not copy, modify, sublicense, or distribute the Program
|
| 182 | +except as expressly provided under this License. Any attempt
|
| 183 | +otherwise to copy, modify, sublicense or distribute the Program is
|
| 184 | +void, and will automatically terminate your rights under this License.
|
| 185 | +However, parties who have received copies, or rights, from you under
|
| 186 | +this License will not have their licenses terminated so long as such
|
| 187 | +parties remain in full compliance.
|
| 188 | +
|
| 189 | + 5. You are not required to accept this License, since you have not
|
| 190 | +signed it. However, nothing else grants you permission to modify or
|
| 191 | +distribute the Program or its derivative works. These actions are
|
| 192 | +prohibited by law if you do not accept this License. Therefore, by
|
| 193 | +modifying or distributing the Program (or any work based on the
|
| 194 | +Program), you indicate your acceptance of this License to do so, and
|
| 195 | +all its terms and conditions for copying, distributing or modifying
|
| 196 | +the Program or works based on it.
|
| 197 | +
|
| 198 | + 6. Each time you redistribute the Program (or any work based on the
|
| 199 | +Program), the recipient automatically receives a license from the
|
| 200 | +original licensor to copy, distribute or modify the Program subject to
|
| 201 | +these terms and conditions. You may not impose any further
|
| 202 | +restrictions on the recipients' exercise of the rights granted herein.
|
| 203 | +You are not responsible for enforcing compliance by third parties to
|
| 204 | +this License.
|
| 205 | +
|
| 206 | + 7. If, as a consequence of a court judgment or allegation of patent
|
| 207 | +infringement or for any other reason (not limited to patent issues),
|
| 208 | +conditions are imposed on you (whether by court order, agreement or
|
| 209 | +otherwise) that contradict the conditions of this License, they do not
|
| 210 | +excuse you from the conditions of this License. If you cannot
|
| 211 | +distribute so as to satisfy simultaneously your obligations under this
|
| 212 | +License and any other pertinent obligations, then as a consequence you
|
| 213 | +may not distribute the Program at all. For example, if a patent
|
| 214 | +license would not permit royalty-free redistribution of the Program by
|
| 215 | +all those who receive copies directly or indirectly through you, then
|
| 216 | +the only way you could satisfy both it and this License would be to
|
| 217 | +refrain entirely from distribution of the Program.
|
| 218 | +
|
| 219 | +If any portion of this section is held invalid or unenforceable under
|
| 220 | +any particular circumstance, the balance of the section is intended to
|
| 221 | +apply and the section as a whole is intended to apply in other
|
| 222 | +circumstances.
|
| 223 | +
|
| 224 | +It is not the purpose of this section to induce you to infringe any
|
| 225 | +patents or other property right claims or to contest validity of any
|
| 226 | +such claims; this section has the sole purpose of protecting the
|
| 227 | +integrity of the free software distribution system, which is
|
| 228 | +implemented by public license practices. Many people have made
|
| 229 | +generous contributions to the wide range of software distributed
|
| 230 | +through that system in reliance on consistent application of that
|
| 231 | +system; it is up to the author/donor to decide if he or she is willing
|
| 232 | +to distribute software through any other system and a licensee cannot
|
| 233 | +impose that choice.
|
| 234 | +
|
| 235 | +This section is intended to make thoroughly clear what is believed to
|
| 236 | +be a consequence of the rest of this License.
|
| 237 | +
|
| 238 | + 8. If the distribution and/or use of the Program is restricted in
|
| 239 | +certain countries either by patents or by copyrighted interfaces, the
|
| 240 | +original copyright holder who places the Program under this License
|
| 241 | +may add an explicit geographical distribution limitation excluding
|
| 242 | +those countries, so that distribution is permitted only in or among
|
| 243 | +countries not thus excluded. In such case, this License incorporates
|
| 244 | +the limitation as if written in the body of this License.
|
| 245 | +
|
| 246 | + 9. The Free Software Foundation may publish revised and/or new versions
|
| 247 | +of the General Public License from time to time. Such new versions will
|
| 248 | +be similar in spirit to the present version, but may differ in detail to
|
| 249 | +address new problems or concerns.
|
| 250 | +
|
| 251 | +Each version is given a distinguishing version number. If the Program
|
| 252 | +specifies a version number of this License which applies to it and "any
|
| 253 | +later version", you have the option of following the terms and conditions
|
| 254 | +either of that version or of any later version published by the Free
|
| 255 | +Software Foundation. If the Program does not specify a version number of
|
| 256 | +this License, you may choose any version ever published by the Free Software
|
| 257 | +Foundation.
|
| 258 | +
|
| 259 | + 10. If you wish to incorporate parts of the Program into other free
|
| 260 | +programs whose distribution conditions are different, write to the author
|
| 261 | +to ask for permission. For software which is copyrighted by the Free
|
| 262 | +Software Foundation, write to the Free Software Foundation; we sometimes
|
| 263 | +make exceptions for this. Our decision will be guided by the two goals
|
| 264 | +of preserving the free status of all derivatives of our free software and
|
| 265 | +of promoting the sharing and reuse of software generally.
|
| 266 | +
|
| 267 | + NO WARRANTY
|
| 268 | +
|
| 269 | + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
| 270 | +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
| 271 | +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
| 272 | +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
| 273 | +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
| 274 | +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
| 275 | +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
| 276 | +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
| 277 | +REPAIR OR CORRECTION.
|
| 278 | +
|
| 279 | + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
| 280 | +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
| 281 | +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
| 282 | +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
| 283 | +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
| 284 | +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
| 285 | +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
| 286 | +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
| 287 | +POSSIBILITY OF SUCH DAMAGES.
|
| 288 | +
|
| 289 | + END OF TERMS AND CONDITIONS
|
| 290 | +
|
| 291 | + How to Apply These Terms to Your New Programs
|
| 292 | +
|
| 293 | + If you develop a new program, and you want it to be of the greatest
|
| 294 | +possible use to the public, the best way to achieve this is to make it
|
| 295 | +free software which everyone can redistribute and change under these terms.
|
| 296 | +
|
| 297 | + To do so, attach the following notices to the program. It is safest
|
| 298 | +to attach them to the start of each source file to most effectively
|
| 299 | +convey the exclusion of warranty; and each file should have at least
|
| 300 | +the "copyright" line and a pointer to where the full notice is found.
|
| 301 | +
|
| 302 | + <one line to give the program's name and a brief idea of what it does.>
|
| 303 | + Copyright (C) <year> <name of author>
|
| 304 | +
|
| 305 | + This program is free software; you can redistribute it and/or modify
|
| 306 | + it under the terms of the GNU General Public License as published by
|
| 307 | + the Free Software Foundation; either version 2 of the License, or
|
| 308 | + (at your option) any later version.
|
| 309 | +
|
| 310 | + This program is distributed in the hope that it will be useful,
|
| 311 | + but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 312 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 313 | + GNU General Public License for more details.
|
| 314 | +
|
| 315 | + You should have received a copy of the GNU General Public License
|
| 316 | + along with this program; if not, write to the Free Software
|
| 317 | + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
| 318 | +
|
| 319 | +
|
| 320 | +Also add information on how to contact you by electronic and paper mail.
|
| 321 | +
|
| 322 | +If the program is interactive, make it output a short notice like this
|
| 323 | +when it starts in an interactive mode:
|
| 324 | +
|
| 325 | + Gnomovision version 69, Copyright (C) year name of author
|
| 326 | + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
| 327 | + This is free software, and you are welcome to redistribute it
|
| 328 | + under certain conditions; type `show c' for details.
|
| 329 | +
|
| 330 | +The hypothetical commands `show w' and `show c' should show the appropriate
|
| 331 | +parts of the General Public License. Of course, the commands you use may
|
| 332 | +be called something other than `show w' and `show c'; they could even be
|
| 333 | +mouse-clicks or menu items--whatever suits your program.
|
| 334 | +
|
| 335 | +You should also get your employer (if you work as a programmer) or your
|
| 336 | +school, if any, to sign a "copyright disclaimer" for the program, if
|
| 337 | +necessary. Here is a sample; alter the names:
|
| 338 | +
|
| 339 | + Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
| 340 | + `Gnomovision' (which makes passes at compilers) written by James Hacker.
|
| 341 | +
|
| 342 | + <signature of Ty Coon>, 1 April 1989
|
| 343 | + Ty Coon, President of Vice
|
| 344 | +
|
| 345 | +This General Public License does not permit incorporating your program into
|
| 346 | +proprietary programs. If your program is a subroutine library, you may
|
| 347 | +consider it more useful to permit linking proprietary applications with the
|
| 348 | +library. If this is what you want to do, use the GNU Library General
|
| 349 | +Public License instead of this License.
|
Index: trunk/extensions/SemanticNotifyMe/README |
— | — | @@ -0,0 +1,29 @@ |
| 2 | +== About ==
|
| 3 | +
|
| 4 | +Semantic NotifyMe is a project for extending Semantic MediaWiki extension with
|
| 5 | +"realtime notification" functions that allows users to get realtime notifications
|
| 6 | +on semantic property-value changes. For details and further links, see
|
| 7 | +http://www.mediawiki.org/wiki/Extension:Semantic_NotifyMe.
|
| 8 | +
|
| 9 | +Notes on installing Semantic NotifyMe are found in the file INSTALL.
|
| 10 | +
|
| 11 | +== Contact ==
|
| 12 | +
|
| 13 | +If you have remarks, questions, or suggestions, please send them to
|
| 14 | +ning@teammersion.com.
|
| 15 | +
|
| 16 | +Bugs should be filed at MediaZilla, http://bugzilla.wikimedia.org/.
|
| 17 | +
|
| 18 | +== Developers ==
|
| 19 | +
|
| 20 | +Development is coordinated by Ning Hu and Justin Zhang.
|
| 21 | +
|
| 22 | +Semantic NotifyMe development is funded by Vulcan Inc.
|
| 23 | +Semantic NotifyMe has also been supported in the Halo
|
| 24 | +project funded by Vulcan Inc., where development is coordinated
|
| 25 | +by ontoprise GmbH, Karlsruhe, Germany.
|
| 26 | +
|
| 27 | +== Contributors ==
|
| 28 | +
|
| 29 | +N/A
|
| 30 | +
|