drupal-7.26/0000755001412200141220000000000012265564172012223 5ustar benderbenderdrupal-7.26/index.php0000644001412200141220000000102112265562324014032 0ustar benderbender (S2EL1~%Ŕ͝=Mz̏Dl&>9`/\tIVK[aǖ=KWZ DkӧB5tR@ϙ&J-aFyH"-B =Dq>@tO>#|:@9t c7߈c 6d4020  cL1 ",b .1"J(DP)HB%h !d,(d d&('+dd,0d/1d43d89d:=d??d#ABdȚGHd %%KMd --PRd 22TTd77UVd<>YЈH[ШHH](IM_ȄhĄ&/dH4zD1)M)H$M njYqb".LdPSiʷ/=MӔnTp%2MD"EVZe͐%kDX`%xj+-[bEbQHL5 ӧK"葦I!d,( dd (+0d139=? #B"%H* M. -R5 2T67V;ZCD\FE]JI_L!LcN$tҔNb6ɾN%o^#x5RW\#r5m[#lӪ5왱FȔ5biX1`zeV.ZbreiU+TJeSM0ijT $FY !d,(( dd +0d139=? #B"%H* -M. R5 2T67V;ZCD\FE]JI_L!LcN$\%E)4F#~ *o#x9RW#r9m#lӪ9v4E.%[LѰb.ҭ\.ŚҪV.:%ҧP8EDɑH+E)!d,(d( dd +0d139=? #B"%H* -M. 2R5 T67V;ZCD\FE]JI_L!LcN$&=$&JISIO_A'O O-ZgrAl][,4ieRlLČM[ خLt$֫L\d $RBqiJ25zI@!d,(dd( +0d139=? #B"%H* -M. 2R5 7T6V;ZCD\FE]JI_L!LcN$&=$&JISO?PRZCD\FE]JI_L!LcN$&=$&JISO?yr/t֙B.Tqim5Mְ-r E4)cH&b "[pHWZ ҌQ4q ԤJ e$I!d,(dd( +0d13d9=? #B"%H* -M. 2R5 7T6X@ZCD\FE]JI_L!LcN$&=$&JISO?ys:u$my[DM5li&m2Mʘ-E4Ȗ&\4UE9P 5&N TL=$iP !d,(d( +0d139d=? #B"%H* -M. 2R5 7T6X@DZC\FE]JI_L!LcN$&=$&JISO?yijRIԕ5n8MXe۶c5Mְ-r E4)cH&b آ[rZKSWXJ(RLM I.A4#H!d,(d( +0d139= d?#B"%H* -M. 2R5 7T6X@DZCE\F]JI_L!LcN$&=$&JISO?MA7uЩ+IoMҦ ilLr E4)cH&bkע[~Ђ$+HNd*Ԩx8yjRK2Mj$2!d,(dd( +dd0139=? "#dB %H*-M. 2R5 7T6X@DZCE\FI]J_L!LcN$"=$&JHSH)>F"ѳ;x(SW8r(q׵H(QYcɖ1FXD( ׬ZdzňթTLԦNYHS$GM !d,(d( +dd0139=? B"#* %dH -M.2R5 7T6X@DZCE\FI]JL_L!đcN$"=$&JHS( h`$}Qg/%wQB%qQ-%kئ1F(-F 0FrbkV-FZbT*FBbjS'F(Y/#H!d,(d( +dd0139=? B"#H* %. -dM 2R57T6X@DZCE\FI]JL_L!OcNH($&JHS(؏>~(ѳ;x(SW8r(q󖍒5lE֌3JƐ)k"_*z)+YjS 5Mid SIAb!d,( d( d+d0139=? B"#H* %M. -5 2dR7T6X@DZCE\FI]JL_L!OcN$My&&Jg7)>{Ճ7;uΥ#7)8ou[5i11cȞ ɮEplV-EXZE@ZIE&UH$G!d,(d d( +0139=? B"#H* %M. -R5 267dTX@DZCE\FI]JL_L!OcNH8bML6I{ًGuЩ+GI9G&uh$*Ҩ-kرd 5!"5W:ʑPɑ&J0-jD $C!d,(dd ( +0d139=? B"#H* %M. -R5 2T67;X@DZCE\FI]JL_L!OcNH8ILoߦJ:M92Wi7G*YɶM5k̜A3L0atM?b9jMV-:%P:}ZDIӥLi$P !d,(ހd ( +0139=? B"#H* %M. -R5 2T67V;<@>XDZCE\FI]JL_L!OcNH8JLo_yBUrJ9:W ˷pشqF3gЌ!SL1]|Ѳ+,Yzr UEDE8]ʴ'H!d,(ހdd ( d+0d139=? B"#H* %M. -R5 2T67V;CDZE\FI]JL_L!Oc`4()O?F,ջwȝ%xPbݷpm6ڵfϢK,ذbzu+׫X:j))Q,ZCDFE\I]JL_L!Oc`4()(}ѳdOޡwc9WcD6n=fGΠ12F ##^{d #]bͲN4#ԦNUI!G"M !d,(d ( d+0d139=? B"#H* %M. -R5 2T67V;ZCD\FEJI]!L_Oc`4()Ȩ#~ճE#x1RW#rb#oYc3h=BGˆ12/Fje),WVBe)Q@=d)&J0=rd)R@;drupal-7.26/misc/throbber.gif0000644001412200141220000000247012265562324015454 0ustar benderbenderGIF89a(s {{5FZk؋ᵵス! NETSCAPE2.0!,( Yۨ/[4PenBQ1(+"($Q pBWS%r"ͦn> LJ'{v~  {+*  #   " "  *++ !!, G Ӓڨ"7^G!qD0*O"`p.LisJX 5rLd,3uY G+i!, ? Xc*pʭM89#: %H(UȭVa'FӘHcZV^(!, H T(P "HŨM p8ȄqJhD1rwMgta!I, !, I Ӓڨ"7^Xl~# z'&Hxjc"S9 3yzyS`pZ!, @ Xc*pPܺ.xD !*J@0J ]xJc:Q:y!, E (P "Xw"Gb"LQ%I U&&.8 CNQ!, F ڨ"7^Xl~# WP[,'rB` bC1A+!, A Xc*pPܺ.xTP#I0PǮE`ul11(0< !, I T(P "H7h @<0PS`) NBbph&QaիS3D!, H Ӓڨ"7^Xc̊W8N ՊS ,] `Uo !, A Xc*pȭA,"q#9P@#+cUV\RDVw ;drupal-7.26/misc/help.png0000644001412200141220000000044612265562324014615 0ustar benderbenderPNG  IHDRaIDATxڥ= @s#HN#"mnb2G-, B ^B2}ܝVYiP4+(kb* 1;d&ZERY-aԸ= - -5}/-a!q bcm53:9 NPq@:@bd{AD3Ag$u;ٜQy#mrlt쫛OB䃪 tIENDB`drupal-7.26/misc/powered-gray-88x31.png0000644001412200141220000000366012265562324017064 0ustar benderbenderPNG  IHDRXTPLTE~~~yyyvvv|||jjjYrrrppptttֶ.JmmmWJYb;_"OjLi?cDZfKfv1Wo Bd7KXciLbpv}_q|.VnRZ_EWb|{\o{FcuKh:]3HTu\v8y,nIDATx^c$K/ncsm۶.m۶m۶m۶'vzwz'b7b9sGQU?f>HOϿieY}jERA_Zf[/Ͱo?j-;*+I DV} (b(]+O-F?݂NUQ*C| @ |6hJۺ&Rxq TKPUULqͥk.a$"6tiyoT K$!A4 h?Z{_yuNM4htg_XuD t3-te477s zfqI9 e߽z$Qxؕ=DZtRNPq;ˇhvđT8JaDaAdPZ" [mhA#v #Y=RqkPlp ukHCO+>>0~j3ŗi5'oFqǑg̩E:SE-csBR ɔ$^ PlfxANm[6DBl רQU4[!ِB1cV{Sϙ0n|%3X`Jr1&c!t1M EgeYzH*L(ؖ mfo;sր_=O K*QQ zE: mɦNa>EхxҲ8D<}F3^J8J"9# gz{zv;^p^8cϟit3AMq~XbZ8x2IENDB`drupal-7.26/misc/message-24-ok.png0000644001412200141220000000204212265562324016135 0ustar benderbenderPNG  IHDRw=IDATx͖_L[U݌& >_e/ۮniro -fYۍ0ʀ&ѕB{0l@V k1ec(cT&AHƇi6S͂-ѓ|r=~{1*@?Ek6O(>W0U! YC ޭu ]r f][ u0P.N%dLP~HզQ*X&-kR3nDsc1NOJ5*jX+F娜|6@~`8kMv 3"3Cs&Lwv/cF=4,Fg!9e2;X۫Rh0j@ x)Z}.ӂt:x{a8c&EBd e1AZ4׿o8ʼ 렋P7sgn@PG(s \fBЃ4Bq9$`w˿/c{@ QWжioͲY2BB!m$(Jтg=~N3I>D!S$x~tyax}-7ηgoς').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); drupal-7.26/misc/menu-collapsed-rtl.png0000644001412200141220000000015312265562324017367 0ustar benderbenderPNG  IHDRŐPLTEU~tRNS@fIDATc``R`r` B| OIENDB`drupal-7.26/misc/ajax.js0000644001412200141220000005401412265562324014440 0ustar benderbender(function ($) { /** * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML). * * Ajax is a method of making a request via JavaScript while viewing an HTML * page. The request returns an array of commands encoded in JSON, which is * then executed to make any changes that are necessary to the page. * * Drupal uses this file to enhance form elements with #ajax['path'] and * #ajax['wrapper'] properties. If set, this file will automatically be included * to provide Ajax capabilities. */ Drupal.ajax = Drupal.ajax || {}; /** * Attaches the Ajax behavior to each Ajax form element. */ Drupal.behaviors.AJAX = { attach: function (context, settings) { // Load all Ajax behaviors specified in the settings. for (var base in settings.ajax) { if (!$('#' + base + '.ajax-processed').length) { var element_settings = settings.ajax[base]; if (typeof element_settings.selector == 'undefined') { element_settings.selector = '#' + base; } $(element_settings.selector).each(function () { element_settings.element = this; Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); }); $('#' + base).addClass('ajax-processed'); } } // Bind Ajax behaviors to all items showing the class. $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () { var element_settings = {}; // Clicked links look better with the throbber than the progress bar. element_settings.progress = { 'type': 'throbber' }; // For anchor tags, these will go to the target of the anchor rather // than the usual location. if ($(this).attr('href')) { element_settings.url = $(this).attr('href'); element_settings.event = 'click'; } var base = $(this).attr('id'); Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); }); // This class means to submit the form to the action using Ajax. $('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () { var element_settings = {}; // Ajax submits specified in this manner automatically submit to the // normal form action. element_settings.url = $(this.form).attr('action'); // Form submit button clicks need to tell the form what was clicked so // it gets passed in the POST request. element_settings.setClick = true; // Form buttons use the 'click' event rather than mousedown. element_settings.event = 'click'; // Clicked form buttons look better with the throbber than the progress bar. element_settings.progress = { 'type': 'throbber' }; var base = $(this).attr('id'); Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); }); } }; /** * Ajax object. * * All Ajax objects on a page are accessible through the global Drupal.ajax * object and are keyed by the submit button's ID. You can access them from * your module's JavaScript file to override properties or functions. * * For example, if your Ajax enabled button has the ID 'edit-submit', you can * redefine the function that is called to insert the new content like this * (inside a Drupal.behaviors attach block): * @code * Drupal.behaviors.myCustomAJAXStuff = { * attach: function (context, settings) { * Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) { * new_content = $(response.data); * $('#my-wrapper').append(new_content); * alert('New content was appended to #my-wrapper'); * } * } * }; * @endcode */ Drupal.ajax = function (base, element, element_settings) { var defaults = { url: 'system/ajax', event: 'mousedown', keypress: true, selector: '#' + base, effect: 'none', speed: 'none', method: 'replaceWith', progress: { type: 'throbber', message: Drupal.t('Please wait...') }, submit: { 'js': true } }; $.extend(this, defaults, element_settings); this.element = element; this.element_settings = element_settings; // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let // the server detect when it needs to degrade gracefully. // There are five scenarios to check for: // 1. /nojs/ // 2. /nojs$ - The end of a URL string. // 3. /nojs? - Followed by a query (with clean URLs enabled). // E.g.: path/nojs?destination=foobar // 4. /nojs& - Followed by a query (without clean URLs enabled). // E.g.: ?q=path/nojs&destination=foobar // 5. /nojs# - Followed by a fragment. // E.g.: path/nojs#myfragment this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1'); this.wrapper = '#' + element_settings.wrapper; // If there isn't a form, jQuery.ajax() will be used instead, allowing us to // bind Ajax to links as well. if (this.element.form) { this.form = $(this.element.form); } // Set the options for the ajaxSubmit function. // The 'this' variable will not persist inside of the options object. var ajax = this; ajax.options = { url: ajax.url, data: ajax.submit, beforeSerialize: function (element_settings, options) { return ajax.beforeSerialize(element_settings, options); }, beforeSubmit: function (form_values, element_settings, options) { ajax.ajaxing = true; return ajax.beforeSubmit(form_values, element_settings, options); }, beforeSend: function (xmlhttprequest, options) { ajax.ajaxing = true; return ajax.beforeSend(xmlhttprequest, options); }, success: function (response, status) { // Sanity check for browser support (object expected). // When using iFrame uploads, responses must be returned as a string. if (typeof response == 'string') { response = $.parseJSON(response); } return ajax.success(response, status); }, complete: function (response, status) { ajax.ajaxing = false; if (status == 'error' || status == 'parsererror') { return ajax.error(response, ajax.url); } }, dataType: 'json', type: 'POST' }; // Bind the ajaxSubmit function to the element event. $(ajax.element).bind(element_settings.event, function (event) { return ajax.eventResponse(this, event); }); // If necessary, enable keyboard submission so that Ajax behaviors // can be triggered through keyboard input as well as e.g. a mousedown // action. if (element_settings.keypress) { $(ajax.element).keypress(function (event) { return ajax.keypressResponse(this, event); }); } // If necessary, prevent the browser default action of an additional event. // For example, prevent the browser default action of a click, even if the // AJAX behavior binds to mousedown. if (element_settings.prevent) { $(ajax.element).bind(element_settings.prevent, false); } }; /** * Handle a key press. * * The Ajax object will, if instructed, bind to a key press response. This * will test to see if the key press is valid to trigger this event and * if it is, trigger it for us and prevent other keypresses from triggering. * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13 * and 32. RETURN is often used to submit a form when in a textfield, and * SPACE is often used to activate an element without submitting. */ Drupal.ajax.prototype.keypressResponse = function (element, event) { // Create a synonym for this to reduce code confusion. var ajax = this; // Detect enter key and space bar and allow the standard response for them, // except for form elements of type 'text' and 'textarea', where the // spacebar activation causes inappropriate activation if #ajax['keypress'] is // TRUE. On a text-type widget a space should always be a space. if (event.which == 13 || (event.which == 32 && element.type != 'text' && element.type != 'textarea')) { $(ajax.element_settings.element).trigger(ajax.element_settings.event); return false; } }; /** * Handle an event that triggers an Ajax response. * * When an event that triggers an Ajax response happens, this method will * perform the actual Ajax call. It is bound to the event using * bind() in the constructor, and it uses the options specified on the * ajax object. */ Drupal.ajax.prototype.eventResponse = function (element, event) { // Create a synonym for this to reduce code confusion. var ajax = this; // Do not perform another ajax command if one is already in progress. if (ajax.ajaxing) { return false; } try { if (ajax.form) { // If setClick is set, we must set this to ensure that the button's // value is passed. if (ajax.setClick) { // Mark the clicked button. 'form.clk' is a special variable for // ajaxSubmit that tells the system which element got clicked to // trigger the submit. Without it there would be no 'op' or // equivalent. element.form.clk = element; } ajax.form.ajaxSubmit(ajax.options); } else { ajax.beforeSerialize(ajax.element, ajax.options); $.ajax(ajax.options); } } catch (e) { // Unset the ajax.ajaxing flag here because it won't be unset during // the complete response. ajax.ajaxing = false; alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message); } // For radio/checkbox, allow the default event. On IE, this means letting // it actually check the box. if (typeof element.type != 'undefined' && (element.type == 'checkbox' || element.type == 'radio')) { return true; } else { return false; } }; /** * Handler for the form serialization. * * Runs before the beforeSend() handler (see below), and unlike that one, runs * before field data is collected. */ Drupal.ajax.prototype.beforeSerialize = function (element, options) { // Allow detaching behaviors to update field values before collecting them. // This is only needed when field values are added to the POST data, so only // when there is a form such that this.form.ajaxSubmit() is used instead of // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize() // isn't called, but don't rely on that: explicitly check this.form. if (this.form) { var settings = this.settings || Drupal.settings; Drupal.detachBehaviors(this.form, settings, 'serialize'); } // Prevent duplicate HTML ids in the returned markup. // @see drupal_html_id() options.data['ajax_html_ids[]'] = []; $('[id]').each(function () { options.data['ajax_html_ids[]'].push(this.id); }); // Allow Drupal to return new JavaScript and CSS files to load without // returning the ones already loaded. // @see ajax_base_page_theme() // @see drupal_get_css() // @see drupal_get_js() options.data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme; options.data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token; for (var key in Drupal.settings.ajaxPageState.css) { options.data['ajax_page_state[css][' + key + ']'] = 1; } for (var key in Drupal.settings.ajaxPageState.js) { options.data['ajax_page_state[js][' + key + ']'] = 1; } }; /** * Modify form values prior to form submission. */ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { // This function is left empty to make it simple to override for modules // that wish to add functionality here. }; /** * Prepare the Ajax request before it is sent. */ Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) { // For forms without file inputs, the jQuery Form plugin serializes the form // values, and then calls jQuery's $.ajax() function, which invokes this // handler. In this circumstance, options.extraData is never used. For forms // with file inputs, the jQuery Form plugin uses the browser's normal form // submission mechanism, but captures the response in a hidden IFRAME. In this // circumstance, it calls this handler first, and then appends hidden fields // to the form to submit the values in options.extraData. There is no simple // way to know which submission mechanism will be used, so we add to extraData // regardless, and allow it to be ignored in the former case. if (this.form) { options.extraData = options.extraData || {}; // Let the server know when the IFRAME submission mechanism is used. The // server can use this information to wrap the JSON response in a TEXTAREA, // as per http://jquery.malsup.com/form/#file-upload. options.extraData.ajax_iframe_upload = '1'; // The triggering element is about to be disabled (see below), but if it // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that // value is included in the submission. As per above, submissions that use // $.ajax() are already serialized prior to the element being disabled, so // this is only needed for IFRAME submissions. var v = $.fieldValue(this.element); if (v !== null) { options.extraData[this.element.name] = v; } } // Disable the element that received the change to prevent user interface // interaction while the Ajax request is in progress. ajax.ajaxing prevents // the element from triggering a new request, but does not prevent the user // from changing its value. $(this.element).addClass('progress-disabled').attr('disabled', true); // Insert progressbar or throbber. if (this.progress.type == 'bar') { var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback)); if (this.progress.message) { progressBar.setProgress(-1, this.progress.message); } if (this.progress.url) { progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500); } this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar'); this.progress.object = progressBar; $(this.element).after(this.progress.element); } else if (this.progress.type == 'throbber') { this.progress.element = $('
 
'); if (this.progress.message) { $('.throbber', this.progress.element).after('
' + this.progress.message + '
'); } $(this.element).after(this.progress.element); } }; /** * Handler for the form redirection completion. */ Drupal.ajax.prototype.success = function (response, status) { // Remove the progress element. if (this.progress.element) { $(this.progress.element).remove(); } if (this.progress.object) { this.progress.object.stopMonitoring(); } $(this.element).removeClass('progress-disabled').removeAttr('disabled'); Drupal.freezeHeight(); for (var i in response) { if (response.hasOwnProperty(i) && response[i]['command'] && this.commands[response[i]['command']]) { this.commands[response[i]['command']](this, response[i], status); } } // Reattach behaviors, if they were detached in beforeSerialize(). The // attachBehaviors() called on the new content from processing the response // commands is not sufficient, because behaviors from the entire form need // to be reattached. if (this.form) { var settings = this.settings || Drupal.settings; Drupal.attachBehaviors(this.form, settings); } Drupal.unfreezeHeight(); // Remove any response-specific settings so they don't get used on the next // call by mistake. this.settings = null; }; /** * Build an effect object which tells us how to apply the effect when adding new HTML. */ Drupal.ajax.prototype.getEffect = function (response) { var type = response.effect || this.effect; var speed = response.speed || this.speed; var effect = {}; if (type == 'none') { effect.showEffect = 'show'; effect.hideEffect = 'hide'; effect.showSpeed = ''; } else if (type == 'fade') { effect.showEffect = 'fadeIn'; effect.hideEffect = 'fadeOut'; effect.showSpeed = speed; } else { effect.showEffect = type + 'Toggle'; effect.hideEffect = type + 'Toggle'; effect.showSpeed = speed; } return effect; }; /** * Handler for the form redirection error. */ Drupal.ajax.prototype.error = function (response, uri) { alert(Drupal.ajaxError(response, uri)); // Remove the progress element. if (this.progress.element) { $(this.progress.element).remove(); } if (this.progress.object) { this.progress.object.stopMonitoring(); } // Undo hide. $(this.wrapper).show(); // Re-enable the element. $(this.element).removeClass('progress-disabled').removeAttr('disabled'); // Reattach behaviors, if they were detached in beforeSerialize(). if (this.form) { var settings = response.settings || this.settings || Drupal.settings; Drupal.attachBehaviors(this.form, settings); } }; /** * Provide a series of commands that the server can request the client perform. */ Drupal.ajax.prototype.commands = { /** * Command to insert new content into the DOM. */ insert: function (ajax, response, status) { // Get information from the response. If it is not there, default to // our presets. var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper); var method = response.method || ajax.method; var effect = ajax.getEffect(response); // We don't know what response.data contains: it might be a string of text // without HTML, so don't rely on jQuery correctly iterpreting // $(response.data) as new HTML rather than a CSS selector. Also, if // response.data contains top-level text nodes, they get lost with either // $(response.data) or $('
').replaceWith(response.data). var new_content_wrapped = $('
').html(response.data); var new_content = new_content_wrapped.contents(); // For legacy reasons, the effects processing code assumes that new_content // consists of a single top-level element. Also, it has not been // sufficiently tested whether attachBehaviors() can be successfully called // with a context object that includes top-level text nodes. However, to // give developers full control of the HTML appearing in the page, and to // enable Ajax content to be inserted in places where DIV elements are not // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new // content satisfies the requirement of a single top-level element, and // only use the container DIV created above when it doesn't. For more // information, please see http://drupal.org/node/736066. if (new_content.length != 1 || new_content.get(0).nodeType != 1) { new_content = new_content_wrapped; } // If removing content from the wrapper, detach behaviors first. switch (method) { case 'html': case 'replaceWith': case 'replaceAll': case 'empty': case 'remove': var settings = response.settings || ajax.settings || Drupal.settings; Drupal.detachBehaviors(wrapper, settings); } // Add the new content to the page. wrapper[method](new_content); // Immediately hide the new content if we're using any effects. if (effect.showEffect != 'show') { new_content.hide(); } // Determine which effect to use and what content will receive the // effect, then show the new content. if ($('.ajax-new-content', new_content).length > 0) { $('.ajax-new-content', new_content).hide(); new_content.show(); $('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed); } else if (effect.showEffect != 'show') { new_content[effect.showEffect](effect.showSpeed); } // Attach all JavaScript behaviors to the new content, if it was successfully // added to the page, this if statement allows #ajax['wrapper'] to be // optional. if (new_content.parents('html').length > 0) { // Apply any settings from the returned JSON if available. var settings = response.settings || ajax.settings || Drupal.settings; Drupal.attachBehaviors(new_content, settings); } }, /** * Command to remove a chunk from the page. */ remove: function (ajax, response, status) { var settings = response.settings || ajax.settings || Drupal.settings; Drupal.detachBehaviors($(response.selector), settings); $(response.selector).remove(); }, /** * Command to mark a chunk changed. */ changed: function (ajax, response, status) { if (!$(response.selector).hasClass('ajax-changed')) { $(response.selector).addClass('ajax-changed'); if (response.asterisk) { $(response.selector).find(response.asterisk).append(' * '); } } }, /** * Command to provide an alert. */ alert: function (ajax, response, status) { alert(response.text, response.title); }, /** * Command to provide the jQuery css() function. */ css: function (ajax, response, status) { $(response.selector).css(response.argument); }, /** * Command to set the settings that will be used for other commands in this response. */ settings: function (ajax, response, status) { if (response.merge) { $.extend(true, Drupal.settings, response.settings); } else { ajax.settings = response.settings; } }, /** * Command to attach data using jQuery's data API. */ data: function (ajax, response, status) { $(response.selector).data(response.name, response.value); }, /** * Command to apply a jQuery method. */ invoke: function (ajax, response, status) { var $element = $(response.selector); $element[response.method].apply($element, response.arguments); }, /** * Command to restripe a table. */ restripe: function (ajax, response, status) { // :even and :odd are reversed because jQuery counts from 0 and // we count from 1, so we're out of sync. // Match immediate children of the parent element to allow nesting. $('> tbody > tr:visible, > tr:visible', $(response.selector)) .removeClass('odd even') .filter(':even').addClass('odd').end() .filter(':odd').addClass('even'); } }; })(jQuery); drupal-7.26/misc/message-16-error.png0000644001412200141220000000100712265562324016656 0ustar benderbenderPNG  IHDRaIDATxڥ?HBQƣr #p(2hAk !GP2ISiъ :W_O"sw^gH^3כT(Ppc"31!h>_af Epd!H ߯! LNj Mp7<ǃpl2LIlo#6; a!vp˅r4 u RpNE>yHnlG.RWR B?%a<a2#=4}DrOu_ TWXTPjmA(`%xZZjKjFUtNjX'';GWXCW83p~zo$B{;rtAjX) |!uwsֆhx-=2ةQ-riIMQ(r?6{L~_ͧGIENDB`drupal-7.26/misc/message-16-info.png0000644001412200141220000000133512265562324016464 0ustar benderbenderPNG  IHDRaIDATxmKSaPBP zv.;RѴAT:Ew&NtHc-\y,I奝3S*_ 60o|Q8;ǯs4q(sAdv^=uX ]`3XCI{C"d[BY9') .attr('id', $input.attr('id') + '-autocomplete-aria-live') ); new Drupal.jsAC($input, acdb[uri]); }); } }; /** * Prevents the form from submitting if the suggestions popup is open * and closes the suggestions popup when doing so. */ Drupal.autocompleteSubmit = function () { return $('#autocomplete').each(function () { this.owner.hidePopup(); }).length == 0; }; /** * An AutoComplete object. */ Drupal.jsAC = function ($input, db) { var ac = this; this.input = $input[0]; this.ariaLive = $('#' + this.input.id + '-autocomplete-aria-live'); this.db = db; $input .keydown(function (event) { return ac.onkeydown(this, event); }) .keyup(function (event) { ac.onkeyup(this, event); }) .blur(function () { ac.hidePopup(); ac.db.cancel(); }); }; /** * Handler for the "keydown" event. */ Drupal.jsAC.prototype.onkeydown = function (input, e) { if (!e) { e = window.event; } switch (e.keyCode) { case 40: // down arrow. this.selectDown(); return false; case 38: // up arrow. this.selectUp(); return false; default: // All other keys. return true; } }; /** * Handler for the "keyup" event. */ Drupal.jsAC.prototype.onkeyup = function (input, e) { if (!e) { e = window.event; } switch (e.keyCode) { case 16: // Shift. case 17: // Ctrl. case 18: // Alt. case 20: // Caps lock. case 33: // Page up. case 34: // Page down. case 35: // End. case 36: // Home. case 37: // Left arrow. case 38: // Up arrow. case 39: // Right arrow. case 40: // Down arrow. return true; case 9: // Tab. case 13: // Enter. case 27: // Esc. this.hidePopup(e.keyCode); return true; default: // All other keys. if (input.value.length > 0 && !input.readOnly) { this.populatePopup(); } else { this.hidePopup(e.keyCode); } return true; } }; /** * Puts the currently highlighted suggestion into the autocomplete field. */ Drupal.jsAC.prototype.select = function (node) { this.input.value = $(node).data('autocompleteValue'); }; /** * Highlights the next suggestion. */ Drupal.jsAC.prototype.selectDown = function () { if (this.selected && this.selected.nextSibling) { this.highlight(this.selected.nextSibling); } else if (this.popup) { var lis = $('li', this.popup); if (lis.length > 0) { this.highlight(lis.get(0)); } } }; /** * Highlights the previous suggestion. */ Drupal.jsAC.prototype.selectUp = function () { if (this.selected && this.selected.previousSibling) { this.highlight(this.selected.previousSibling); } }; /** * Highlights a suggestion. */ Drupal.jsAC.prototype.highlight = function (node) { if (this.selected) { $(this.selected).removeClass('selected'); } $(node).addClass('selected'); this.selected = node; $(this.ariaLive).html($(this.selected).html()); }; /** * Unhighlights a suggestion. */ Drupal.jsAC.prototype.unhighlight = function (node) { $(node).removeClass('selected'); this.selected = false; $(this.ariaLive).empty(); }; /** * Hides the autocomplete suggestions. */ Drupal.jsAC.prototype.hidePopup = function (keycode) { // Select item if the right key or mousebutton was pressed. if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) { this.input.value = $(this.selected).data('autocompleteValue'); } // Hide popup. var popup = this.popup; if (popup) { this.popup = null; $(popup).fadeOut('fast', function () { $(popup).remove(); }); } this.selected = false; $(this.ariaLive).empty(); }; /** * Positions the suggestions popup and starts a search. */ Drupal.jsAC.prototype.populatePopup = function () { var $input = $(this.input); var position = $input.position(); // Show popup. if (this.popup) { $(this.popup).remove(); } this.selected = false; this.popup = $('
')[0]; this.popup.owner = this; $(this.popup).css({ top: parseInt(position.top + this.input.offsetHeight, 10) + 'px', left: parseInt(position.left, 10) + 'px', width: $input.innerWidth() + 'px', display: 'none' }); $input.before(this.popup); // Do search. this.db.owner = this; this.db.search(this.input.value); }; /** * Fills the suggestion popup with any matches received. */ Drupal.jsAC.prototype.found = function (matches) { // If no value in the textfield, do not show the popup. if (!this.input.value.length) { return false; } // Prepare matches. var ul = $('
    '); var ac = this; for (key in matches) { $('
  • ') .html($('
    ').html(matches[key])) .mousedown(function () { ac.select(this); }) .mouseover(function () { ac.highlight(this); }) .mouseout(function () { ac.unhighlight(this); }) .data('autocompleteValue', key) .appendTo(ul); } // Show popup with matches, if any. if (this.popup) { if (ul.children().length) { $(this.popup).empty().append(ul).show(); $(this.ariaLive).html(Drupal.t('Autocomplete popup')); } else { $(this.popup).css({ visibility: 'hidden' }); this.hidePopup(); } } }; Drupal.jsAC.prototype.setStatus = function (status) { switch (status) { case 'begin': $(this.input).addClass('throbbing'); $(this.ariaLive).html(Drupal.t('Searching for matches...')); break; case 'cancel': case 'error': case 'found': $(this.input).removeClass('throbbing'); break; } }; /** * An AutoComplete DataBase object. */ Drupal.ACDB = function (uri) { this.uri = uri; this.delay = 300; this.cache = {}; }; /** * Performs a cached and delayed search. */ Drupal.ACDB.prototype.search = function (searchString) { var db = this; this.searchString = searchString; // See if this string needs to be searched for anyway. searchString = searchString.replace(/^\s+|\s+$/, ''); if (searchString.length <= 0 || searchString.charAt(searchString.length - 1) == ',') { return; } // See if this key has been searched for before. if (this.cache[searchString]) { return this.owner.found(this.cache[searchString]); } // Initiate delayed search. if (this.timer) { clearTimeout(this.timer); } this.timer = setTimeout(function () { db.owner.setStatus('begin'); // Ajax GET request for autocompletion. We use Drupal.encodePath instead of // encodeURIComponent to allow autocomplete search terms to contain slashes. $.ajax({ type: 'GET', url: db.uri + '/' + Drupal.encodePath(searchString), dataType: 'json', success: function (matches) { if (typeof matches.status == 'undefined' || matches.status != 0) { db.cache[searchString] = matches; // Verify if these are still the matches the user wants to see. if (db.searchString == searchString) { db.owner.found(matches); } db.owner.setStatus('found'); } }, error: function (xmlhttp) { alert(Drupal.ajaxError(xmlhttp, db.uri)); } }); }, this.delay); }; /** * Cancels the current autocomplete request. */ Drupal.ACDB.prototype.cancel = function () { if (this.owner) this.owner.setStatus('cancel'); if (this.timer) clearTimeout(this.timer); this.searchString = ''; }; })(jQuery); drupal-7.26/misc/draggable.png0000644001412200141220000000041412265562324015570 0ustar benderbenderPNG  IHDR<{-PLTErrr{{{VktRNSAHIDATxN!w*BX`B1b?B/",YIcb{?皲^% cG< g grvt^}[M>?: QJ Z NAr !?#%U%IENDB`drupal-7.26/misc/permissions.png0000644001412200141220000000036212265562324016235 0ustar benderbenderPNG  IHDRaIDATxڕ D /BrM.F+ W{ʼnQJMlѽ@`#`:hxkEAIENDB`drupal-7.26/misc/powered-blue-80x15.png0000644001412200141220000000165712265562324017047 0ustar benderbenderPNG  IHDRPDJPLTEZ%!c{ks{9&sks!kk){1kkscJ)s$w159֭J)sJBscZsZZsm؜kcsBZƽkBk{{1ΥR91Z)R9R{Rέ޵ZJޔRƭJZcJνJ!BZRޔkk} IDATx^E#;@03363303>)[zY|-yqtͿ;;1OBzu[ۏ5 !B:8!#+*]>\l[۞NJ*ZA859{FFC*>n7</JD|h TZ}pVM0vZhv_?QZR_Q㘰\,d̫^'y#@ӄ,d8͡1$t. XےU66 6AQ퀛u#VRAE16̩Cǰ]l9+; \= " 1 f2g&@7=3Uvm*bL9ī`J`EKJh{c>rrl)I3eye@P(22giӖϲ>DR ͒Q9  TL1LW2My-:Jw׷"'#h{rB @S2 4DbG R~ *EY=>2Gg]&IENDB`drupal-7.26/misc/batch.js0000644001412200141220000000165312265562324014577 0ustar benderbender(function ($) { /** * Attaches the batch behavior to progress bars. */ Drupal.behaviors.batch = { attach: function (context, settings) { $('#progress', context).once('batch', function () { var holder = $(this); // Success: redirect to the summary. var updateCallback = function (progress, status, pb) { if (progress == 100) { pb.stopMonitoring(); window.location = settings.batch.uri + '&op=finished'; } }; var errorCallback = function (pb) { holder.prepend($('

    ').html(settings.batch.errorMessage)); $('#wait').hide(); }; var progress = new Drupal.progressBar('updateprogress', updateCallback, 'POST', errorCallback); progress.setProgress(-1, settings.batch.initMessage); holder.append(progress.element); progress.startMonitoring(settings.batch.uri + '&op=do', 10); }); } }; })(jQuery); drupal-7.26/misc/tree.png0000644001412200141220000000020212265562324014612 0ustar benderbenderPNG  IHDRPQ8#ɛ PLTEU6tRNS@f'IDAT8OcX040`QQQAhTpTr 9 I"IENDB`drupal-7.26/misc/message-24-help.png0000644001412200141220000000210012265562324016447 0ustar benderbenderPNG  IHDRw=IDATx͖{L[U݌&faG?_]4޶@1 XAAa7x2([ NAP&llηEٮp/N$#F\끱~ov!A[T TRgCiGZ%>ˡpSU蛖%$A^Dk_B]p2 -5tqS>T= u3v/0Wrq|NxFg۟ -z]\Cc)h/ 5 EYZ׶< ܝH\-Eb'[jAR~(zFC E\ٍ# "A3,{zJL<,J7ۛPۯ"!8XI1;DT˙ J=,L_qkw Mb*Pm Zz$Pc@<AR$!oVn$E$ɬ $OYBS5OS=T`R8nh^"mlk)RRĬ> ֙%=zn'P *630U "Aٙgg@c!0ǣ,[ mw(aR][liG])3HjB&GQbdycPw+}G[rKA^b V-bEȭ30'J>ؒ[gI euEޱ7e7#sHc_^0!tf UΉX; r@? ^8/.\9E'%&{˗.,3iZ)%gWןr|rc:99eΩm.w['.='y{_F/(IENDB`drupal-7.26/misc/message-16-warning.png0000644001412200141220000000067212265562324017201 0ustar benderbenderPNG  IHDRaIDAT8Oc?%_r@aP op|W58%ɀK<7MPl,B]UvG>~ +8>Q4k˭1^.Z]k(P h@sGN cx h4Xѯ{QT0 qPk|anrp#+2oa52[|i v^ةYd=sU,0 (N{0K_]ˇ"d͇(Td-hS@Q©`.Ci܀Xr`(\?I`n1¯tT7aIQ=l0;n)[`£4gIENDB`drupal-7.26/misc/druplicon.png0000644001412200141220000000750112265562324015663 0ustar benderbenderPNG  IHDRXd-IDATx] PTW}FͦĤTe2L*$:LM1jԸwA6*;"H@\AEAQDE]dߡY/{74O-}{}0t$>ʁMѵo!'w";W{]ue3? ݲ1bM)#4SD@GkQM3$V?!< c/o"BAzPd\7Bm(x=[~ ṍ,{S8Jg׉0vh# BNy=ywxN*.RǖFѱbWudd AB1ڡOG.gOҔTӌR`<o Z8H$do*[L+ZDQr?Tshq X m,0[qNT2b:BKG4.Ȋ K9rW˂1\W<.OBRާ$ ^A,% +Jj[ ()"³YĠz6ןzs5JƸA$3k= #+r*X#`w:z ۯtE a=iqF&4C\fSbѦDbx]JLՕ) O vJWR|qe˙"eŸ^ǡMm,km8^97M|ֻ|\nFE ؊6,?4Lz{CCngD4-dŻ>,={B+JZ k^-̈!]Sȏ'AiV\rPkSTOk+?["͗#J~ _)F6c'ډ7Df7/Hg_ u-])qgKb:4m/6p^XVip;?<.PYC&nf:;ž =F o9J޴=+XO$ξ xݖ9L*ЋzoJlv>6r\d0zԇ݋mizn#bI^L}T3&m9K/7u%x+NxmxP8>Ex,#M˚J/t`}lBbz,d,]"}LKQ|Gz/&YJ2`}G*=.!ڶ<>NDJ5{69a,'K&Ʉ;z!"y<L#]c_=\}B"ߗ0<73 `l^,s:AM9K "lYlr:m LDU1\+M8ZV ~SG; +[C"}قJ|f% b'\2:1ER-SАdv! %Nr"CHۑ`lkep +K`q>4AFpi Aop/z14JڀV6ćW6, 67 Dp +CAJA qn,;y O mw5> PnUăuִ-2pWۯ*Ȍ_vH$kZ4IYYq SB"luVԇ>'qM#Wz\G%%8q\v%( K\"MZM;JwkN!#Y'sCbx2n3̋g <+{E]6AAP% 0/mC ͒6*i 1эx=HNp#Z`K#3Ws8i xlR`4PUCRfz$(( Z0&vL%7\.юCKQ"BI'L4Â"Tf9vXG\oI'LDy UjH F}_O.hm8T iyevM:gx}?G>\ Pȃ//,hFZ[:P>olE&w`20˝w#v0sߩTZŬ,9D`Ły\<f z+el`Zޙ8@^$c CMӢ{tH(B^y-YLR(6L}mdEV/2zT*Y~#`qG!*V%IcqϘz+ N̡[Cdu-9 3Ngr3=9;Nr!W:.DF&g5.{A FS̜1r#2CӲl_rv Y 7=n! 4~lY¿P0(si+1G{KHr;NzMY]ye4M|}Ex\$ɱ #/ H2 GXH #3b%W <؟Ѭ GKD^Tbf!u0{E{n7툥)*)\cRgf'>S( [=`XfJDfAfj-Enà `K}tXz>v,eGZw;("dub?ԑ|ѓ}! T>>9fz(ŭ*GX3{?t2c QfܣjqDiвzq"쯃 '60EZ`ur[' zeM. >Q AJy}ǚFfIe4-d2$3>o>*[ք!Q>X3RrJ= Ёhv9ӐBf'A 6ߝWyܮ0_^ޟ0LK2$\jHAeIT?ϲٮBZ(9$eojXbfm(a-L5 4M{H'0=+p5 E3˽&H%%Ǯg {kO %oQuz7 BI!<ߎ>pO'#*'ɰxԱ@J4'B_uÿz鬖QPܤO%΍y,^W;Ǫa%Dځ83C,hMx{WɈ,/P>b&͊liD^_ann+ˋ Xu24mQȶ}ؐSǼY?::?EP|s!2%z dK &U` KF0, U`<`c F0v5` 㯙C@ f`,FU CNo 3;21]vf\8]v&kΰN*k``#F0&*k;216h$ᵽX?[hnDIENDB`drupal-7.26/misc/message-24-error.png0000644001412200141220000000133512265562324016661 0ustar benderbenderPNG  IHDRw=IDATxڵMHACDŃxS977M+Dq10~}} s{z@ vv"R[XKKPK !1;Ě5%pr3M 쬬2.k:Q쑺ui q:R_H pu5ȧf3޹\y/\O#)pM %1ܭ`q1brOLtn~<_\Do__ବmmr*i SSOjU+QW>">l*NCp2p'p}sU$|j*iiZpeӗGN8pE8 OG": 9h <)HiPHIENDB`drupal-7.26/misc/configure.png0000644001412200141220000000037012265562324015642 0ustar benderbenderPNG  IHDRaIDATxڥQ 0 D!!  %_.A̖l):UUzq'笾~L&T5L @s] k#YqQh yO(w-d'b/nIENDB`drupal-7.26/misc/message-24-info.png0000644001412200141220000000176312265562324016470 0ustar benderbenderPNG  IHDRw=IDATx͖]L[e݌&f^of7Jzsʇcb2EvmQ` cle-J:"SD110@='=KwtE'%Oq@@5ȉ1:`~. M΁i!ch>ֽߐzoGQ;1š)䛽n B^ܖ(rJH(kA$.!< Y.7>ʃEl&mZH$bEST!y*+eىp`発i~D$ 1iXEq(*׸Fd2@x*Q/. |SPږTi| e(L8whQ 鯒Ĥml {{P[cֱٺ MLm#$0P5bkvݹgL\Ɔ$1mSE7ZďɁ2пY#jjgrt;+5:Aidyi۽p]^Y{$C6g] g=., TdJkAk|y-o{%/VX{aQk7;KbW>D3 qIENDB`drupal-7.26/misc/message-16-ok.png0000644001412200141220000000117712265562324016146 0ustar benderbenderPNG  IHDRaFIDATxڥKSaǍ( K9sc?D<8v1s[8""j<ۜrg 6kV&M]+[Taԅo}>|x}:fr1݄ΤNy8 Mt; ]it_C{G :H˲ tS n¾i1?1C@6j <]ԼCC``qP*z>uzw k?tڏH1Biv TQE]p_ʀI38{׎mA|bʨ}" ^ojR ۸>w 1x:W:ѾE\^7$ L OȱrI(x-n8@f r^yL#gEphtKRp.‹1RDvK[vJMeQHV?TyP):Z~6{FvȦeB1G8N2>\E꓂ Zt1f3Y&D'O||$4ƨ"&!@2)%@][}C;wN]-``0Q9O[x>PdIENDB`drupal-7.26/misc/form.js0000644001412200141220000000463412265562324014463 0ustar benderbender(function ($) { /** * Retrieves the summary for the first element. */ $.fn.drupalGetSummary = function () { var callback = this.data('summaryCallback'); return (this[0] && callback) ? $.trim(callback(this[0])) : ''; }; /** * Sets the summary for all matched elements. * * @param callback * Either a function that will be called each time the summary is * retrieved or a string (which is returned each time). */ $.fn.drupalSetSummary = function (callback) { var self = this; // To facilitate things, the callback should always be a function. If it's // not, we wrap it into an anonymous function which just returns the value. if (typeof callback != 'function') { var val = callback; callback = function () { return val; }; } return this .data('summaryCallback', callback) // To prevent duplicate events, the handlers are first removed and then // (re-)added. .unbind('formUpdated.summary') .bind('formUpdated.summary', function () { self.trigger('summaryUpdated'); }) // The actual summaryUpdated handler doesn't fire when the callback is // changed, so we have to do this manually. .trigger('summaryUpdated'); }; /** * Sends a 'formUpdated' event each time a form element is modified. */ Drupal.behaviors.formUpdated = { attach: function (context) { // These events are namespaced so that we can remove them later. var events = 'change.formUpdated click.formUpdated blur.formUpdated keyup.formUpdated'; $(context) // Since context could be an input element itself, it's added back to // the jQuery object and filtered again. .find(':input').andSelf().filter(':input') // To prevent duplicate events, the handlers are first removed and then // (re-)added. .unbind(events).bind(events, function () { $(this).trigger('formUpdated'); }); } }; /** * Prepopulate form fields with information from the visitor cookie. */ Drupal.behaviors.fillUserInfoFromCookie = { attach: function (context, settings) { $('form.user-info-from-cookie').once('user-info-from-cookie', function () { var formContext = this; $.each(['name', 'mail', 'homepage'], function () { var $element = $('[name=' + this + ']', formContext); var cookie = $.cookie('Drupal.visitor.' + this); if ($element.length && cookie) { $element.val(cookie); } }); }); } }; })(jQuery); drupal-7.26/misc/powered-black-135x42.png0000644001412200141220000000521312265562324017245 0ustar benderbenderPNG  IHDR*PLTE,DDk99c22[**U!+T1Z>k /Z!J""K##R++[J B = 7;GGr.XEu 'TSnƅwgQ(s (V$$[@m=4{"O??r5aٚҊ˲+w3cވjU ^DO#.]\ Q/))b!] Keez̳OOY髫6uu33H))8CUUc{{pZ}''-Tr!XWWW}nI{#%d\WWvz 1c__vfX(+j See膰)a V77pZVVѶZ৩(1mPbb[[‰"(fVY`ޣ"c` YO [?Cy&(_Wz-;X#[(8v(q%q ?8} 5]$+jKNoa_U4Ȏ>;mnǀd u48XV~GPu?m_o%mG52n:TF2fv9*RLj .WܲTu>}X s)/8X)xaXu8v8SY1r3,ub |1ctLKcc찋4Gxd@P LNk~o َmyä/:%|K=Axŕ&vȁj IuX;jG{BZ㼉YmY&0C v1.b,,]Hh ҅سX;jKvZkb4mo4ZM u%(;!(̢nܸzezr ̇0t,&7b whqe҅8t99Ьk׃E!Na4X񉤕q3cQC%U˷hŖ/RѢF@'qb]'QD &@_{sV le5qY0?5uO0@`SUt(r/nDl1kft٪po Y !:R@ۦ~63ANޭC[S\j"mXUB]1k{e'롯- ^oM8TqpQ] ¡AEs@ȘϦBzyjb>$hI{  &pD}#ӫf ;,bU 6lR*֬4Zo1lWgjrZCKE>yxvgqv'NaʱOasmsシ};Z)6ׅxSuvC1#PO{w1D) Izg)g~{iƵLO5}VƂ\Jur,ate ñZ(,`T&Þ_ЈSNSNǜs'Psda8D 糘u$O il'l%*ya U0J)qԋMFf+Q" xс@2 &`>%DBtz?נ\u%u\K$;Q|o6ۇBNmsk,`o_R 3:(C񁤗qVBcG|c ?8Pqs&#mHG8?GR3")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b.dialogClass).css({zIndex:b.zIndex}).attr("tabIndex", -1).css("outline",0).keydown(function(i){if(b.closeOnEscape&&i.keyCode&&i.keyCode===c.ui.keyCode.ESCAPE){a.close(i);i.preventDefault()}}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(i){a.moveToTop(false,i)});a.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g);var f=(a.uiDialogTitlebar=c("
    ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),h=c('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role", "button").hover(function(){h.addClass("ui-state-hover")},function(){h.removeClass("ui-state-hover")}).focus(function(){h.addClass("ui-state-focus")}).blur(function(){h.removeClass("ui-state-focus")}).click(function(i){a.close(i);return false}).appendTo(f);(a.uiDialogTitlebarCloseText=c("")).addClass("ui-icon ui-icon-closethick").text(b.closeText).appendTo(h);c("").addClass("ui-dialog-title").attr("id",e).html(d).prependTo(f);if(c.isFunction(b.beforeclose)&&!c.isFunction(b.beforeClose))b.beforeClose= b.beforeclose;f.find("*").add(f).disableSelection();b.draggable&&c.fn.draggable&&a._makeDraggable();b.resizable&&c.fn.resizable&&a._makeResizable();a._createButtons(b.buttons);a._isOpen=false;c.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy();a.uiDialog.hide();a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");a.uiDialog.remove();a.originalTitle&& a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(a){var b=this,d,e;if(false!==b._trigger("beforeClose",a)){b.overlay&&b.overlay.destroy();b.uiDialog.unbind("keypress.ui-dialog");b._isOpen=false;if(b.options.hide)b.uiDialog.hide(b.options.hide,function(){b._trigger("close",a)});else{b.uiDialog.hide();b._trigger("close",a)}c.ui.dialog.overlay.resize();if(b.options.modal){d=0;c(".ui-dialog").each(function(){if(this!==b.uiDialog[0]){e=c(this).css("z-index"); isNaN(e)||(d=Math.max(d,e))}});c.ui.dialog.maxZ=d}return b}},isOpen:function(){return this._isOpen},moveToTop:function(a,b){var d=this,e=d.options;if(e.modal&&!a||!e.stack&&!e.modal)return d._trigger("focus",b);if(e.zIndex>c.ui.dialog.maxZ)c.ui.dialog.maxZ=e.zIndex;if(d.overlay){c.ui.dialog.maxZ+=1;d.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=c.ui.dialog.maxZ)}a={scrollTop:d.element.attr("scrollTop"),scrollLeft:d.element.attr("scrollLeft")};c.ui.dialog.maxZ+=1;d.uiDialog.css("z-index",c.ui.dialog.maxZ); d.element.attr(a);d._trigger("focus",b);return d},open:function(){if(!this._isOpen){var a=this,b=a.options,d=a.uiDialog;a.overlay=b.modal?new c.ui.dialog.overlay(a):null;a._size();a._position(b.position);d.show(b.show);a.moveToTop(true);b.modal&&d.bind("keypress.ui-dialog",function(e){if(e.keyCode===c.ui.keyCode.TAB){var g=c(":tabbable",this),f=g.filter(":first");g=g.filter(":last");if(e.target===g[0]&&!e.shiftKey){f.focus(1);return false}else if(e.target===f[0]&&e.shiftKey){g.focus(1);return false}}}); c(a.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus();a._isOpen=true;a._trigger("open");return a}},_createButtons:function(a){var b=this,d=false,e=c("
    ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=c("
    ").addClass("ui-dialog-buttonset").appendTo(e);b.uiDialog.find(".ui-dialog-buttonpane").remove();typeof a==="object"&&a!==null&&c.each(a,function(){return!(d=true)});if(d){c.each(a,function(f, h){h=c.isFunction(h)?{click:h,text:f}:h;f=c('').attr(h,true).unbind("click").click(function(){h.click.apply(b.element[0],arguments)}).appendTo(g);c.fn.button&&f.button()});e.appendTo(b.uiDialog)}},_makeDraggable:function(){function a(f){return{position:f.position,offset:f.offset}}var b=this,d=b.options,e=c(document),g;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(f,h){g= d.height==="auto"?"auto":c(this).height();c(this).height(c(this).height()).addClass("ui-dialog-dragging");b._trigger("dragStart",f,a(h))},drag:function(f,h){b._trigger("drag",f,a(h))},stop:function(f,h){d.position=[h.position.left-e.scrollLeft(),h.position.top-e.scrollTop()];c(this).removeClass("ui-dialog-dragging").height(g);b._trigger("dragStop",f,a(h));c.ui.dialog.overlay.resize()}})},_makeResizable:function(a){function b(f){return{originalPosition:f.originalPosition,originalSize:f.originalSize, position:f.position,size:f.size}}a=a===j?this.options.resizable:a;var d=this,e=d.options,g=d.uiDialog.css("position");a=typeof a==="string"?a:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:a,start:function(f,h){c(this).addClass("ui-dialog-resizing");d._trigger("resizeStart",f,b(h))},resize:function(f,h){d._trigger("resize",f,b(h))},stop:function(f, h){c(this).removeClass("ui-dialog-resizing");e.height=c(this).height();e.width=c(this).width();d._trigger("resizeStop",f,b(h));c.ui.dialog.overlay.resize()}}).css("position",g).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(a){var b=[],d=[0,0],e;if(a){if(typeof a==="string"||typeof a==="object"&&"0"in a){b=a.split?a.split(" "):[a[0],a[1]];if(b.length=== 1)b[1]=b[0];c.each(["left","top"],function(g,f){if(+b[g]===b[g]){d[g]=b[g];b[g]=f}});a={my:b.join(" "),at:b.join(" "),offset:d.join(" ")}}a=c.extend({},c.ui.dialog.prototype.options.position,a)}else a=c.ui.dialog.prototype.options.position;(e=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(c.extend({of:window},a));e||this.uiDialog.hide()},_setOptions:function(a){var b=this,d={},e=false;c.each(a,function(g,f){b._setOption(g,f);if(g in k)e=true;if(g in l)d[g]=f});e&&this._size();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",d)},_setOption:function(a,b){var d=this,e=d.uiDialog;switch(a){case "beforeclose":a="beforeClose";break;case "buttons":d._createButtons(b);break;case "closeText":d.uiDialogTitlebarCloseText.text(""+b);break;case "dialogClass":e.removeClass(d.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b);break;case "disabled":b?e.addClass("ui-dialog-disabled"):e.removeClass("ui-dialog-disabled"); break;case "draggable":var g=e.is(":data(draggable)");g&&!b&&e.draggable("destroy");!g&&b&&d._makeDraggable();break;case "position":d._position(b);break;case "resizable":(g=e.is(":data(resizable)"))&&!b&&e.resizable("destroy");g&&typeof b==="string"&&e.resizable("option","handles",b);!g&&b!==false&&d._makeResizable(b);break;case "title":c(".ui-dialog-title",d.uiDialogTitlebar).html(""+(b||" "));break}c.Widget.prototype._setOption.apply(d,arguments)},_size:function(){var a=this.options,b,d,e= this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0});if(a.minWidth>a.width)a.width=a.minWidth;b=this.uiDialog.css({height:"auto",width:a.width}).height();d=Math.max(0,a.minHeight-b);if(a.height==="auto")if(c.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();a=this.element.css("height","auto").height();e||this.uiDialog.hide();this.element.height(Math.max(a,d))}else this.element.height(Math.max(a.height-b,0));this.uiDialog.is(":data(resizable)")&& this.uiDialog.resizable("option","minHeight",this._minHeight())}});c.extend(c.ui.dialog,{version:"1.8.7",uuid:0,maxZ:0,getTitleId:function(a){a=a.attr("id");if(!a){this.uuid+=1;a=this.uuid}return"ui-dialog-title-"+a},overlay:function(a){this.$el=c.ui.dialog.overlay.create(a)}});c.extend(c.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(a){if(this.instances.length=== 0){setTimeout(function(){c.ui.dialog.overlay.instances.length&&c(document).bind(c.ui.dialog.overlay.events,function(d){if(c(d.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(), height:this.height()});c.fn.bgiframe&&b.bgiframe();this.instances.push(b);return b},destroy:function(a){var b=c.inArray(a,this.instances);b!=-1&&this.oldInstances.push(this.instances.splice(b,1)[0]);this.instances.length===0&&c([document,window]).unbind(".dialog-overlay");a.remove();var d=0;c.each(this.instances,function(){d=Math.max(d,this.css("z-index"))});this.maxZ=d},height:function(){var a,b;if(c.browser.msie&&c.browser.version<7){a=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight); b=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return a=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a); return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;a.target==this._mouseDownEvent.target&&c.data(a.target,this.widgetName+".preventClickEvent", true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); drupal-7.26/misc/ui/jquery.ui.autocomplete.min.js0000644001412200141220000002106112265562324021343 0ustar benderbender /* * jQuery UI Autocomplete 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Autocomplete * * Depends: * jquery.ui.core.js * jquery.ui.widget.js * jquery.ui.position.js */ (function(d){d.widget("ui.autocomplete",{options:{appendTo:"body",delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},_create:function(){var a=this,b=this.element[0].ownerDocument,f;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){f=false;var e=d.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:a._move("previousPage", c);break;case e.PAGE_DOWN:a._move("nextPage",c);break;case e.UP:a._move("previous",c);c.preventDefault();break;case e.DOWN:a._move("next",c);c.preventDefault();break;case e.ENTER:case e.NUMPAD_ENTER:if(a.menu.active){f=true;c.preventDefault()}case e.TAB:if(!a.menu.active)return;a.menu.select(c);break;case e.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay); break}}}).bind("keypress.autocomplete",function(c){if(f){f=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};this.menu=d("
      ").addClass("ui-autocomplete").appendTo(d(this.options.appendTo|| "body",b)[0]).mousedown(function(c){var e=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(g){g.target!==a.element[0]&&g.target!==e&&!d.ui.contains(e,g.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,e){e=e.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:e})&&/^key/.test(c.originalEvent.type)&&a.element.val(e.value)},selected:function(c,e){var g=e.item.data("item.autocomplete"), h=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=h;setTimeout(function(){a.previous=h;a.selectedItem=g},1)}false!==a._trigger("select",c,{item:g})&&a.element.val(g.value);a.term=a.element.val();a.close(c);a.selectedItem=g},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"); this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0])},_initSource:function(){var a=this,b,f;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,e){e(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){f=this.options.source;this.source= function(c,e){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:f,data:c,dataType:"json",success:function(g,h,i){i===a.xhr&&e(g);a.xhr=null},error:function(g){g===a.xhr&&e([]);a.xhr=null}})}}else this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length").data("item.autocomplete",b).append(d("").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}}); d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(a,b){var f=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return f.test(c.label||c.value||c)})}})})(jQuery); (function(d){d.widget("ui.menu",{_create:function(){var a=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(b){if(d(b.target).closest(".ui-menu-item a").length){b.preventDefault();a.select(b)}});this.refresh()},refresh:function(){var a=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", -1).mouseenter(function(b){a.activate(b,d(this).parent())}).mouseleave(function(){a.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var f=b.offset().top-this.element.offset().top,c=this.element.attr("scrollTop"),e=this.element.height();if(f<0)this.element.attr("scrollTop",c+f);else f>=e&&this.element.attr("scrollTop",c+f-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",a,{item:b})}, deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,f){if(this.active){a=this.active[a+"All"](".ui-menu-item").eq(0); a.length?this.activate(f,a):this.activate(f,this.element.children(b))}else this.activate(f,this.element.children(b))},nextPage:function(a){if(this.hasScroll())if(!this.active||this.last())this.activate(a,this.element.children(".ui-menu-item:first"));else{var b=this.active.offset().top,f=this.element.height(),c=this.element.children(".ui-menu-item").filter(function(){var e=d(this).offset().top-b-f+d(this).height();return e<10&&e>-10});c.length||(c=this.element.children(".ui-menu-item:last"));this.activate(a, c)}else this.activate(a,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(a){if(this.hasScroll())if(!this.active||this.first())this.activate(a,this.element.children(".ui-menu-item:last"));else{var b=this.active.offset().top,f=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-b+f-d(this).height();return c<10&&c>-10});result.length||(result=this.element.children(".ui-menu-item:first")); this.activate(a,result)}else this.activate(a,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()").addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary;if(d.primary||d.secondary){b.addClass("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary"));d.primary&&b.prepend("");d.secondary&&b.append("");if(!this.options.text){b.addClass(e?"ui-button-icons-only":"ui-button-icon-only").removeClass("ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary"); this.hasTitle||b.attr("title",c)}}else b.addClass("ui-button-text-only")}}});a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end()}, destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");a.Widget.prototype.destroy.call(this)}})})(jQuery); drupal-7.26/misc/ui/jquery.ui.widget.min.js0000644001412200141220000000631212265562324020127 0ustar benderbender /*! * jQuery UI Widget 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Widget */ (function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h, a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h; e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options, this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")}, widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this}, enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery); drupal-7.26/misc/ui/jquery.ui.tabs.css0000644001412200141220000000254712265562324017175 0ustar benderbender /* * jQuery UI Tabs 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Tabs#theming */ .ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ .ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } .ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } .ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } .ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ .ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } .ui-tabs .ui-tabs-hide { display: none !important; } drupal-7.26/misc/ui/jquery.ui.datepicker.css0000644001412200141220000000771712265562324020363 0ustar benderbender /* * jQuery UI Datepicker 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Datepicker#theming */ .ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } .ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } .ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } .ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } .ui-datepicker .ui-datepicker-prev { left:2px; } .ui-datepicker .ui-datepicker-next { right:2px; } .ui-datepicker .ui-datepicker-prev-hover { left:1px; } .ui-datepicker .ui-datepicker-next-hover { right:1px; } .ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } .ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } .ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } .ui-datepicker select.ui-datepicker-month-year {width: 100%;} .ui-datepicker select.ui-datepicker-month, .ui-datepicker select.ui-datepicker-year { width: 49%;} .ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } .ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } .ui-datepicker td { border: 0; padding: 1px; } .ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } .ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } .ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } /* with multiple calendars */ .ui-datepicker.ui-datepicker-multi { width:auto; } .ui-datepicker-multi .ui-datepicker-group { float:left; } .ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } .ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } .ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } .ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } .ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } .ui-datepicker-row-break { clear:both; width:100%; } /* RTL support */ .ui-datepicker-rtl { direction: rtl; } .ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } .ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } .ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } .ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } .ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } .ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } .ui-datepicker-rtl .ui-datepicker-group { float:right; } .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } /* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ .ui-datepicker-cover { display: none; /*sorry for IE5*/ display/**/: block; /*sorry for IE5*/ position: absolute; /*must have*/ z-index: -1; /*must have*/ filter: mask(); /*must have*/ top: -4px; /*must have*/ left: -4px; /*must have*/ width: 200px; /*must have*/ height: 200px; /*must have*/ } drupal-7.26/misc/ui/images/0000755001412200141220000000000012265562324015035 5ustar benderbenderdrupal-7.26/misc/ui/images/ui-icons_454545_256x240.png0000644001412200141220000001042112265562324021133 0ustar benderbenderPNG  IHDRIJPLTEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEڲNtRNS2P."Tp@f` <BHJZ&0R,4j8D|($ blߝF>n~hhHIDATx]b۶H儒-{iZK:glkn-tIqq? E$dK>$>;PZsVh!Sy0E0}H)-t koܪKp\RϠ .E7 ) *V;~Pe Bx*,=$zDؾ JҸٻ9{ ǸHpqW@"2'B[$ @TiH/b٥96!XHq`DE*R HV!%;" i] dddddddd4y5  Rb@(8CdŪݡ,@T@ibrq0alX!pe, =4bW { 5Ƭhu~(Q^@3="b5XC@JCT76q_5 @,r šɩD)T|O@ ON-ՙ [n@RXIm݋(F @?=0puL;g$@6η K`>п @h գKVn"a" %l@.v$/U^ G:#`` uTtK~ŋZ5T%kxk]\*Q ,҇B44 OXK|yg+_M(lоEO V$T1BXb-|?@ fBXr%'@ҹA\IJ,}BBc\V rh(]tI^}oצo S3 ";ʙb}"߰ ){b$Gwwݾab")T@pF_er6JvШ"mޭM-d76x˰6ӥ;/`>KrP\_^u1%OTM.}Q3.Nس})>-w`a+sy$t)NbFFFFBejnNVn4,A*X*5>PGa 3 {oB &<L[ Nc.öi=`Q@d ͆I.Il`\t[< Cit484-r +f쑱BCB MH iy }>rxp|z;BǏ;burcK4tz1G~`ؚK| ̔>ۡO$~ Ao)0pzz }i`;ADm8n:cfA@s7L Z/..h8or? N93B~o_'`opO- :TG L;7]`B%˛>*wTpM0H}&t ^1'Oqr'2P͡+z,tIW''|en=dzgRm[NStK{҉mؓVt6ҲR`ζN&}B U(rۗ&1%Q''?l׸+&r{jN಻4) `N狌. ߭ ǣ)q 2?n3Hb`} .`pqY1e_bu7e+N_F(DT,L}LLrmP5|x芥1cx DAb`M(7NED~Mz +4BXd.Mzv͈Pd8p<6?8N*x.6ڍ6GFZ)O !lSshssNp8`'0/<s}.@Ǩs7ξO۟VDa5av]m1+3y6۠>@u50Ps51==p *KVҫ܂ݻc$N4(Xr2###c- 賟Lδ>]5.sYs1f0;'̨Yg銛{@9 `aC(=%bo2=n1 jBoS$n#m=i0ci9}oI qT]W%.(؅]z\x f"]o'u䫵tk{v;AC3ֆwwR_#X (xҋ/q%W hpk_IX'b/fXKi"#####QCLi2t 5L0 QiH2;yTOok;ע ٶ`RNg{zy!Kxm?A(vU~mL(`o/!nmX-{v[ dw=n「sdwzn(}Oy~ m ?XU;,V'+ V&JRZ]᧭:zC'-߆@y 4u `Vۓwъ#zP@Q N>2/{\o)W~a3xLw :_Q;=pּdt\'8~3SRP6y+XQ*޺r ̗ѭ*޺r gl/\U^u$|mbVnw \V|D͊NVNy7k<;/E}?E*dzgO ~g/96f cD}% g$QG7o)U Jo,O@0߾Q(;bw:5 NwRN5Iy'K?}:9mֽ*@f@jU9mҫÍ{$ؗ}dFp|%!DdF>}G{@FFFFFFƦQܞH 3 u Mo~vy}mwz<7nP9rWku=|_nz쿳}@IXn?sn~hhHIDATx]b۶H儒-{iZK:glkn-tIqq? E$dK>$>;PZsVh!Sy0E0}H)-t koܪKp\RϠ .E7 ) *V;~Pe Bx*,=$zDؾ JҸٻ9{ ǸHpqW@"2'B[$ @TiH/b٥96!XHq`DE*R HV!%;" i] dddddddd4y5  Rb@(8CdŪݡ,@T@ibrq0alX!pe, =4bW { 5Ƭhu~(Q^@3="b5XC@JCT76q_5 @,r šɩD)T|O@ ON-ՙ [n@RXIm݋(F @?=0puL;g$@6η K`>п @h գKVn"a" %l@.v$/U^ G:#`` uTtK~ŋZ5T%kxk]\*Q ,҇B44 OXK|yg+_M(lоEO V$T1BXb-|?@ fBXr%'@ҹA\IJ,}BBc\V rh(]tI^}oצo S3 ";ʙb}"߰ ){b$Gwwݾab")T@pF_er6JvШ"mޭM-d76x˰6ӥ;/`>KrP\_^u1%OTM.}Q3.Nس})>-w`a+sy$t)NbFFFFBejnNVn4,A*X*5>PGa 3 {oB &<L[ Nc.öi=`Q@d ͆I.Il`\t[< Cit484-r +f쑱BCB MH iy }>rxp|z;BǏ;burcK4tz1G~`ؚK| ̔>ۡO$~ Ao)0pzz }i`;ADm8n:cfA@s7L Z/..h8or? N93B~o_'`opO- :TG L;7]`B%˛>*wTpM0H}&t ^1'Oqr'2P͡+z,tIW''|en=dzgRm[NStK{҉mؓVt6ҲR`ζN&}B U(rۗ&1%Q''?l׸+&r{jN಻4) `N狌. ߭ ǣ)q 2?n3Hb`} .`pqY1e_bu7e+N_F(DT,L}LLrmP5|x芥1cx DAb`M(7NED~Mz +4BXd.Mzv͈Pd8p<6?8N*x.6ڍ6GFZ)O !lSshssNp8`'0/<s}.@Ǩs7ξO۟VDa5av]m1+3y6۠>@u50Ps51==p *KVҫ܂ݻc$N4(Xr2###c- 賟Lδ>]5.sYs1f0;'̨Yg銛{@9 `aC(=%bo2=n1 jBoS$n#m=i0ci9}oI qT]W%.(؅]z\x f"]o'u䫵tk{v;AC3ֆwwR_#X (xҋ/q%W hpk_IX'b/fXKi"#####QCLi2t 5L0 QiH2;yTOok;ע ٶ`RNg{zy!Kxm?A(vU~mL(`o/!nmX-{v[ dw=n「sdwzn(}Oy~ m ?XU;,V'+ V&JRZ]᧭:zC'-߆@y 4u `Vۓwъ#zP@Q N>2/{\o)W~a3xLw :_Q;=pּdt\'8~3SRP6y+XQ*޺r ̗ѭ*޺r gl/\U^u$|mbVnw \V|D͊NVNy7k<;/E}?E*dzgO ~g/96f cD}% g$QG7o)U Jo,O@0߾Q(;bw:5 NwRN5Iy'K?}:9mֽ*@f@jU9mҫÍ{$ؗ}dFp|%!DdF>}G{@FFFFFFƦQܞH 3 u Mo~vy}mwz<7nP9rWku=|_nz쿳}@IXn?sn~hhHIDATx]b۶H儒-{iZK:glkn-tIqq? E$dK>$>;PZsVh!Sy0E0}H)-t koܪKp\RϠ .E7 ) *V;~Pe Bx*,=$zDؾ JҸٻ9{ ǸHpqW@"2'B[$ @TiH/b٥96!XHq`DE*R HV!%;" i] dddddddd4y5  Rb@(8CdŪݡ,@T@ibrq0alX!pe, =4bW { 5Ƭhu~(Q^@3="b5XC@JCT76q_5 @,r šɩD)T|O@ ON-ՙ [n@RXIm݋(F @?=0puL;g$@6η K`>п @h գKVn"a" %l@.v$/U^ G:#`` uTtK~ŋZ5T%kxk]\*Q ,҇B44 OXK|yg+_M(lоEO V$T1BXb-|?@ fBXr%'@ҹA\IJ,}BBc\V rh(]tI^}oצo S3 ";ʙb}"߰ ){b$Gwwݾab")T@pF_er6JvШ"mޭM-d76x˰6ӥ;/`>KrP\_^u1%OTM.}Q3.Nس})>-w`a+sy$t)NbFFFFBejnNVn4,A*X*5>PGa 3 {oB &<L[ Nc.öi=`Q@d ͆I.Il`\t[< Cit484-r +f쑱BCB MH iy }>rxp|z;BǏ;burcK4tz1G~`ؚK| ̔>ۡO$~ Ao)0pzz }i`;ADm8n:cfA@s7L Z/..h8or? N93B~o_'`opO- :TG L;7]`B%˛>*wTpM0H}&t ^1'Oqr'2P͡+z,tIW''|en=dzgRm[NStK{҉mؓVt6ҲR`ζN&}B U(rۗ&1%Q''?l׸+&r{jN಻4) `N狌. ߭ ǣ)q 2?n3Hb`} .`pqY1e_bu7e+N_F(DT,L}LLrmP5|x芥1cx DAb`M(7NED~Mz +4BXd.Mzv͈Pd8p<6?8N*x.6ڍ6GFZ)O !lSshssNp8`'0/<s}.@Ǩs7ξO۟VDa5av]m1+3y6۠>@u50Ps51==p *KVҫ܂ݻc$N4(Xr2###c- 賟Lδ>]5.sYs1f0;'̨Yg銛{@9 `aC(=%bo2=n1 jBoS$n#m=i0ci9}oI qT]W%.(؅]z\x f"]o'u䫵tk{v;AC3ֆwwR_#X (xҋ/q%W hpk_IX'b/fXKi"#####QCLi2t 5L0 QiH2;yTOok;ע ٶ`RNg{zy!Kxm?A(vU~mL(`o/!nmX-{v[ dw=n「sdwzn(}Oy~ m ?XU;,V'+ V&JRZ]᧭:zC'-߆@y 4u `Vۓwъ#zP@Q N>2/{\o)W~a3xLw :_Q;=pּdt\'8~3SRP6y+XQ*޺r ̗ѭ*޺r gl/\U^u$|mbVnw \V|D͊NVNy7k<;/E}?E*dzgO ~g/96f cD}% g$QG7o)U Jo,O@0߾Q(;bw:5 NwRN5Iy'K?}:9mֽ*@f@jU9mҫÍ{$ؗ}dFp|%!DdF>}G{@FFFFFFƦQܞH 3 u Mo~vy}mwz<7nP9rWku=|_nz쿳}@IXn?sn~hhHIDATx]b۶H儒-{iZK:glkn-tIqq? E$dK>$>;PZsVh!Sy0E0}H)-t koܪKp\RϠ .E7 ) *V;~Pe Bx*,=$zDؾ JҸٻ9{ ǸHpqW@"2'B[$ @TiH/b٥96!XHq`DE*R HV!%;" i] dddddddd4y5  Rb@(8CdŪݡ,@T@ibrq0alX!pe, =4bW { 5Ƭhu~(Q^@3="b5XC@JCT76q_5 @,r šɩD)T|O@ ON-ՙ [n@RXIm݋(F @?=0puL;g$@6η K`>п @h գKVn"a" %l@.v$/U^ G:#`` uTtK~ŋZ5T%kxk]\*Q ,҇B44 OXK|yg+_M(lоEO V$T1BXb-|?@ fBXr%'@ҹA\IJ,}BBc\V rh(]tI^}oצo S3 ";ʙb}"߰ ){b$Gwwݾab")T@pF_er6JvШ"mޭM-d76x˰6ӥ;/`>KrP\_^u1%OTM.}Q3.Nس})>-w`a+sy$t)NbFFFFBejnNVn4,A*X*5>PGa 3 {oB &<L[ Nc.öi=`Q@d ͆I.Il`\t[< Cit484-r +f쑱BCB MH iy }>rxp|z;BǏ;burcK4tz1G~`ؚK| ̔>ۡO$~ Ao)0pzz }i`;ADm8n:cfA@s7L Z/..h8or? N93B~o_'`opO- :TG L;7]`B%˛>*wTpM0H}&t ^1'Oqr'2P͡+z,tIW''|en=dzgRm[NStK{҉mؓVt6ҲR`ζN&}B U(rۗ&1%Q''?l׸+&r{jN಻4) `N狌. ߭ ǣ)q 2?n3Hb`} .`pqY1e_bu7e+N_F(DT,L}LLrmP5|x芥1cx DAb`M(7NED~Mz +4BXd.Mzv͈Pd8p<6?8N*x.6ڍ6GFZ)O !lSshssNp8`'0/<s}.@Ǩs7ξO۟VDa5av]m1+3y6۠>@u50Ps51==p *KVҫ܂ݻc$N4(Xr2###c- 賟Lδ>]5.sYs1f0;'̨Yg銛{@9 `aC(=%bo2=n1 jBoS$n#m=i0ci9}oI qT]W%.(؅]z\x f"]o'u䫵tk{v;AC3ֆwwR_#X (xҋ/q%W hpk_IX'b/fXKi"#####QCLi2t 5L0 QiH2;yTOok;ע ٶ`RNg{zy!Kxm?A(vU~mL(`o/!nmX-{v[ dw=n「sdwzn(}Oy~ m ?XU;,V'+ V&JRZ]᧭:zC'-߆@y 4u `Vۓwъ#zP@Q N>2/{\o)W~a3xLw :_Q;=pּdt\'8~3SRP6y+XQ*޺r ̗ѭ*޺r gl/\U^u$|mbVnw \V|D͊NVNy7k<;/E}?E*dzgO ~g/96f cD}% g$QG7o)U Jo,O@0߾Q(;bw:5 NwRN5Iy'K?}:9mֽ*@f@jU9mҫÍ{$ؗ}dFp|%!DdF>}G{@FFFFFFƦQܞH 3 u Mo~vy}mwz<7nP9rWku=|_nz쿳}@IXn?sn~hhHIDATx]b۶H儒-{iZK:glkn-tIqq? E$dK>$>;PZsVh!Sy0E0}H)-t koܪKp\RϠ .E7 ) *V;~Pe Bx*,=$zDؾ JҸٻ9{ ǸHpqW@"2'B[$ @TiH/b٥96!XHq`DE*R HV!%;" i] dddddddd4y5  Rb@(8CdŪݡ,@T@ibrq0alX!pe, =4bW { 5Ƭhu~(Q^@3="b5XC@JCT76q_5 @,r šɩD)T|O@ ON-ՙ [n@RXIm݋(F @?=0puL;g$@6η K`>п @h գKVn"a" %l@.v$/U^ G:#`` uTtK~ŋZ5T%kxk]\*Q ,҇B44 OXK|yg+_M(lоEO V$T1BXb-|?@ fBXr%'@ҹA\IJ,}BBc\V rh(]tI^}oצo S3 ";ʙb}"߰ ){b$Gwwݾab")T@pF_er6JvШ"mޭM-d76x˰6ӥ;/`>KrP\_^u1%OTM.}Q3.Nس})>-w`a+sy$t)NbFFFFBejnNVn4,A*X*5>PGa 3 {oB &<L[ Nc.öi=`Q@d ͆I.Il`\t[< Cit484-r +f쑱BCB MH iy }>rxp|z;BǏ;burcK4tz1G~`ؚK| ̔>ۡO$~ Ao)0pzz }i`;ADm8n:cfA@s7L Z/..h8or? N93B~o_'`opO- :TG L;7]`B%˛>*wTpM0H}&t ^1'Oqr'2P͡+z,tIW''|en=dzgRm[NStK{҉mؓVt6ҲR`ζN&}B U(rۗ&1%Q''?l׸+&r{jN಻4) `N狌. ߭ ǣ)q 2?n3Hb`} .`pqY1e_bu7e+N_F(DT,L}LLrmP5|x芥1cx DAb`M(7NED~Mz +4BXd.Mzv͈Pd8p<6?8N*x.6ڍ6GFZ)O !lSshssNp8`'0/<s}.@Ǩs7ξO۟VDa5av]m1+3y6۠>@u50Ps51==p *KVҫ܂ݻc$N4(Xr2###c- 賟Lδ>]5.sYs1f0;'̨Yg銛{@9 `aC(=%bo2=n1 jBoS$n#m=i0ci9}oI qT]W%.(؅]z\x f"]o'u䫵tk{v;AC3ֆwwR_#X (xҋ/q%W hpk_IX'b/fXKi"#####QCLi2t 5L0 QiH2;yTOok;ע ٶ`RNg{zy!Kxm?A(vU~mL(`o/!nmX-{v[ dw=n「sdwzn(}Oy~ m ?XU;,V'+ V&JRZ]᧭:zC'-߆@y 4u `Vۓwъ#zP@Q N>2/{\o)W~a3xLw :_Q;=pּdt\'8~3SRP6y+XQ*޺r ̗ѭ*޺r gl/\U^u$|mbVnw \V|D͊NVNy7k<;/E}?E*dzgO ~g/96f cD}% g$QG7o)U Jo,O@0߾Q(;bw:5 NwRN5Iy'K?}:9mֽ*@f@jU9mҫÍ{$ؗ}dFp|%!DdF>}G{@FFFFFFƦQܞH 3 u Mo~vy}mwz<7nP9rWku=|_nz쿳}@IXn?sIDAT81 0Cџ $CB}1@)e_ƅ`I8-%cM0 )" LIENDB`drupal-7.26/misc/ui/images/ui-bg_glass_55_fbf9ee_1x400.png0000644001412200141220000000017012265562324022322 0ustar benderbenderPNG  IHDRoX ?IDAT81 0Bѯl`6Cs<]:[&BA e7lQJŜQY*IENDB`drupal-7.26/misc/ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png0000644001412200141220000000014512265562324024173 0ustar benderbenderPNG  IHDRdG,Z`,IDATcx&!DJqш/Cc ;:*COIENDB`drupal-7.26/misc/ui/images/ui-bg_glass_65_ffffff_1x400.png0000644001412200141220000000015112265562324022405 0ustar benderbenderPNG  IHDRoX 0IDAT8! + ̼JHR)[lk=O_(<` H"IENDB`drupal-7.26/misc/ui/images/ui-bg_flat_0_aaaaaa_40x100.png0000644001412200141220000000026412265562324022156 0ustar benderbenderPNG  IHDR(ddrz{IDATh1 17Y$t3;_TUAUPTUAUPTUAUPTUAUPTUAUPTUAUPTUAUPTUAUPTUAUPTUAUPTUAUPTUAUPTüŝc)IENDB`drupal-7.26/misc/ui/jquery.ui.sortable.min.js0000644001412200141220000005621212265562324020463 0ustar benderbender /* * jQuery UI Sortable 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Sortables * * Depends: * jquery.ui.core.js * jquery.ui.mouse.js * jquery.ui.widget.js */ (function(d){d.widget("ui.sortable",d.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){this.containerCache={};this.element.addClass("ui-sortable"); this.refresh();this.floating=this.items.length?/left|right/.test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a==="disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(this, arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&&!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem= c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset, {click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment(); if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start", a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a);return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute"); if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0],e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a, c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset();c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]== document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp();this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var b=this.containers.length-1;b>=0;b--){this.containers[b]._trigger("deactivate", null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem): d(this.domPosition.parent).prepend(this.currentItem);return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});!c.length&&a.key&&c.push(a.key+"=");return c.join("&")},toArray:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute|| "id")||"")});return c},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+jg&&b+la[this.floating?"width":"height"]?j:g0?"down":"up")}, _getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith();if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h=d.data(f[g],"sortable");if(h&&h!= this&&!h.options.disabled)c.push([d.isFunction(h.options.items)?h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)});return d(b)},_removeCurrentsFromItems:function(){for(var a= this.currentItem.find(":data(sortable-item)"),b=0;b=0;f--)for(var g=d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable"); if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)?i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h= 0;b--){var c=this.items[b],e=this.options.toleranceElement?d(this.options.toleranceElement,c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b=this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top=e.top;this.containers[b].containerCache.width= this.containers[b].element.outerWidth();this.containers[b].containerCache.height=this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f=d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!e)f.style.visibility="hidden";return f}, update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b= null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0],this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length===1){this.containers[c]._trigger("over",a,this._uiHash(this)); this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b=1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h-f)this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.topthis.containment[3])? g:!(g-this.offset.click.topthis.containment[2])?f:!(f-this.offset.click.left=0;e--)if(d.ui.contains(this.containers[e].element[0],this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive", g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update",g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this,this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out",g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over= 0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop",a,this._uiHash());for(e=0;ethis.containment[2])e=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/ b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.topthis.containment[3])?g:!(g-this.offset.click.topthis.containment[2])?e:!(e-this.offset.click.left').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")})}, stop:function(){d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("opacity"))b._opacity=a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable");if(a.scrollParent[0]!= document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){if(!c.axis||c.axis!="x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY=0;h--){var i=c.snapElements[h].left,k=i+c.snapElements[h].width,j=c.snapElements[h].top,l=j+c.snapElements[h].height;if(i-e li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,b=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"); a.headers=a.element.find(b.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){b.disabled||c(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){b.disabled||c(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){b.disabled||c(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){b.disabled||c(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom"); if(b.navigation){var d=a.element.find("a").filter(b.navigationFilter).eq(0);if(d.length){var f=d.closest(".ui-accordion-header");a.active=f.length?f:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||b.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion", function(g){return a._keydown(g)}).next().attr("role","tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);c.browser.safari||a.headers.find("a").attr("tabIndex",-1);b.event&&a.headers.bind(b.event.split(" ").join(".accordion ")+".accordion",function(g){a._clickHandler.call(a,g,this);g.preventDefault()})},_createIcons:function(){var a=this.options;if(a.icons){c("").addClass("ui-icon "+ a.icons.header).prependTo(this.headers);this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabIndex"); this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(a.autoHeight||a.fillHeight)b.css("height","");return c.Widget.prototype.destroy.call(this)},_setOption:function(a,b){c.Widget.prototype._setOption.apply(this,arguments);a=="active"&&this.activate(b);if(a=="icons"){this._destroyIcons(); b&&this._createIcons()}if(a=="disabled")this.headers.add(this.headers.next())[b?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!(this.options.disabled||a.altKey||a.ctrlKey)){var b=c.ui.keyCode,d=this.headers.length,f=this.headers.index(a.target),g=false;switch(a.keyCode){case b.RIGHT:case b.DOWN:g=this.headers[(f+1)%d];break;case b.LEFT:case b.UP:g=this.headers[(f-1+d)%d];break;case b.SPACE:case b.ENTER:this._clickHandler({target:a.target},a.target); a.preventDefault()}if(g){c(a.target).attr("tabIndex",-1);c(g).attr("tabIndex",0);g.focus();return false}return true}},resize:function(){var a=this.options,b;if(a.fillSpace){if(c.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}b=this.element.parent().height();c.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){b-=c(this).outerHeight(true)});this.headers.next().each(function(){c(this).height(Math.max(0,b-c(this).innerHeight()+ c(this).height()))}).css("overflow","auto")}else if(a.autoHeight){b=0;this.headers.next().each(function(){b=Math.max(b,c(this).height("").height())}).height(b)}return this},activate:function(a){this.options.active=a;a=this._findActive(a)[0];this._clickHandler({target:a},a);return this},_findActive:function(a){return a?typeof a==="number"?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):a===false?c([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,b){var d=this.options; if(!d.disabled)if(a.target){a=c(a.currentTarget||b);b=a[0]===this.active[0];d.active=d.collapsible&&b?false:this.headers.index(a);if(!(this.running||!d.collapsible&&b)){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);if(!b){a.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected); a.next().addClass("ui-accordion-content-active")}h=a.next();f=this.active.next();g={options:d,newHeader:b&&d.collapsible?c([]):a,oldHeader:this.active,newContent:b&&d.collapsible?c([]):h,oldContent:f};d=this.headers.index(this.active[0])>this.headers.index(a[0]);this.active=b?c([]):a;this._toggle(h,f,g,b,d)}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header); this.active.next().addClass("ui-accordion-content-active");var f=this.active.next(),g={options:d,newHeader:c([]),oldHeader:d.active,newContent:c([]),oldContent:f},h=this.active=c([]);this._toggle(h,f,g)}},_toggle:function(a,b,d,f,g){var h=this,e=h.options;h.toShow=a;h.toHide=b;h.data=d;var j=function(){if(h)return h._completed.apply(h,arguments)};h._trigger("changestart",null,h.data);h.running=b.size()===0?a.size():b.size();if(e.animated){d={};d=e.collapsible&&f?{toShow:c([]),toHide:b,complete:j, down:g,autoHeight:e.autoHeight||e.fillSpace}:{toShow:a,toHide:b,complete:j,down:g,autoHeight:e.autoHeight||e.fillSpace};if(!e.proxied)e.proxied=e.animated;if(!e.proxiedDuration)e.proxiedDuration=e.duration;e.animated=c.isFunction(e.proxied)?e.proxied(d):e.proxied;e.duration=c.isFunction(e.proxiedDuration)?e.proxiedDuration(d):e.proxiedDuration;f=c.ui.accordion.animations;var i=e.duration,k=e.animated;if(k&&!f[k]&&!c.easing[k])k="slide";f[k]||(f[k]=function(l){this.slide(l,{easing:k,duration:i||700})}); f[k](d)}else{if(e.collapsible&&f)a.toggle();else{b.hide();a.show()}j(true)}b.prev().attr({"aria-expanded":"false",tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");this._trigger("change",null,this.data)}}});c.extend(c.ui.accordion,{version:"1.8.7",animations:{slide:function(a, b){a=c.extend({easing:"swing",duration:300},a,b);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),f=0,g={},h={},e;b=a.toShow;e=b[0].style.width;b.width(parseInt(b.parent().width(),10)-parseInt(b.css("paddingLeft"),10)-parseInt(b.css("paddingRight"),10)-(parseInt(b.css("borderLeftWidth"),10)||0)-(parseInt(b.css("borderRightWidth"),10)||0));c.each(["height","paddingTop","paddingBottom"],function(j,i){h[i]="hide";j=(""+c.css(a.toShow[0],i)).match(/^([\d+-.]+)(.*)$/);g[i]={value:j[1], unit:j[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(h,{step:function(j,i){if(i.prop=="height")f=i.end-i.start===0?0:(i.now-i.start)/(i.end-i.start);a.toShow[0].style[i.prop]=f*g[i.prop].value+g[i.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:e,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide",paddingTop:"hide", paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1E3:200})}}})})(jQuery); drupal-7.26/misc/ui/jquery.ui.droppable.min.js0000644001412200141220000001321212265562324020611 0ustar benderbender /* * jQuery UI Droppable 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Droppables * * Depends: * jquery.ui.core.js * jquery.ui.widget.js * jquery.ui.mouse.js * jquery.ui.draggable.js */ (function(d){d.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"},_create:function(){var a=this.options,b=a.accept;this.isover=0;this.isout=1;this.accept=d.isFunction(b)?b:function(c){return c.is(b)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};d.ui.ddmanager.droppables[a.scope]=d.ui.ddmanager.droppables[a.scope]||[];d.ui.ddmanager.droppables[a.scope].push(this); a.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){for(var a=d.ui.ddmanager.droppables[this.options.scope],b=0;b=j&&f<=l||h>=j&&h<=l||fl)&&(e>= i&&e<=k||g>=i&&g<=k||ek);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
    • #{label}
    • "},_create:function(){this._tabify(true)},_setOption:function(b,e){if(b=="selected")this.options.collapsible&& e==this.options.selected||this.select(e);else{this.options[b]=e;this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[b].concat(d.makeArray(arguments)))},_ui:function(b,e){return{tab:b,panel:e,index:this.anchors.index(b)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b= d(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(b){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var a=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]|| (q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))a.panels=a.panels.add(a.element.find(a._sanitizeSelector(i)));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=a._tabId(f);f.href="#"+i;f=a.element.find("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(a.panels[g-1]||a.list);f.data("destroy.tabs",true)}a.panels=a.panels.add(f)}else c.disabled.push(g)});if(b){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(a._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected= this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return a.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); if(c.selected>=0&&this.anchors.length){a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");a.element.queue("tabs",function(){a._trigger("show",null,a._ui(a.anchors[c.selected],a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash))))});this.load(c.selected)}d(window).bind("unload",function(){a.lis.add(a.anchors).unbind(".tabs");a.lis=a.anchors=a.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); this.element[c.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);b=0;for(var j;j=this.lis[b];b++)d(j)[d.inArray(b,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+ g)};this.lis.bind("mouseover.tabs",function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal", function(){e(f,o);a._trigger("show",null,a._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");a._trigger("show",null,a._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);a.element.dequeue("tabs")})}:function(g,f){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");a.element.dequeue("tabs")}; this.anchors.bind(c.event+".tabs",function(){var g=this,f=d(g).closest("li"),i=a.panels.filter(":not(.ui-tabs-hide)"),l=a.element.find(a._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||a.panels.filter(":animated").length||a._trigger("select",null,a._ui(this,l[0]))===false){this.blur();return false}c.selected=a.anchors.index(this);a.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected= -1;c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this));this.blur();return false}c.cookie&&a._cookie(c.selected,c.cookie);if(l.length){i.length&&a.element.queue("tabs",function(){s(g,i)});a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(b){if(typeof b=="string")b=this.anchors.index(this.anchors.filter("[href$="+b+"]"));return b},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e= d.data(this,"href.tabs");if(e)this.href=e;var a=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){a.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});b.cookie&&this._cookie(null,b.cookie);return this},add:function(b, e,a){if(a===p)a=this.anchors.length;var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,b).replace(/#\{label\}/g,e));b=!b.indexOf("#")?b.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=c.element.find("#"+b);j.length||(j=d(h.panelTemplate).attr("id",b).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(a>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[a]); j.insertBefore(this.panels[a])}h.disabled=d.map(h.disabled,function(k){return k>=a?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[a],this.panels[a]));return this},remove:function(b){b=this._getIndex(b);var e=this.options,a=this.lis.eq(b).remove(),c=this.panels.eq(b).remove(); if(a.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(b+(b+1=b?--h:h});this._tabify();this._trigger("remove",null,this._ui(a.find("a")[0],c[0]));return this},enable:function(b){b=this._getIndex(b);var e=this.options;if(d.inArray(b,e.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(a){return a!=b});this._trigger("enable",null, this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(b){b=this._getIndex(b);var e=this.options;if(b!=e.selected){this.lis.eq(b).addClass("ui-state-disabled");e.disabled.push(b);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[b],this.panels[b]))}return this},select:function(b){b=this._getIndex(b);if(b==-1)if(this.options.collapsible&&this.options.selected!=-1)b=this.options.selected;else return this;this.anchors.eq(b).trigger(this.options.event+".tabs");return this}, load:function(b){b=this._getIndex(b);var e=this,a=this.options,c=this.anchors.eq(b)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(a.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(a.spinner)}this.xhr=d.ajax(d.extend({},a.ajaxOptions,{url:h,success:function(k,n){e.element.find(e._sanitizeSelector(c.hash)).html(k);e._cleanup();a.cache&&d.data(c, "cache.tabs",true);e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.error(k,n,b,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this}, url:function(b,e){this.anchors.eq(b).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.7"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(b,e){var a=this,c=this.options,h=a._rotate||(a._rotate=function(j){clearTimeout(a.rotation);a.rotation=setTimeout(function(){var k=c.selected;a.select(++k").appendTo(this.element);this.oldValue=this._value();this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"); this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===d)return this._value();this._setOption("value",a);return this},_setOption:function(a,c){if(a==="value"){this.options.value=c;this._refreshValue();this._value()===this.options.max&&this._trigger("complete")}b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;if(typeof a!=="number")a=0;return Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100* this._value()/this.options.max},_refreshValue:function(){var a=this.value(),c=this._percentage();if(this.oldValue!==a){this.oldValue=a;this._trigger("change")}this.valueDiv.toggleClass("ui-corner-right",a===this.options.max).width(c.toFixed(0)+"%");this.element.attr("aria-valuenow",a)}});b.extend(b.ui.progressbar,{version:"1.8.7"})})(jQuery); drupal-7.26/misc/ui/jquery.effects.fade.min.js0000644001412200141220000000110112265562324020534 0ustar benderbender /* * jQuery UI Effects Fade 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/Fade * * Depends: * jquery.effects.core.js */ (function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); drupal-7.26/misc/ui/jquery.effects.highlight.min.js0000644001412200141220000000162212265562324021614 0ustar benderbender /* * jQuery UI Effects Highlight 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/Highlight * * Depends: * jquery.effects.core.js */ (function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&& this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery); drupal-7.26/misc/ui/jquery.ui.datepicker.min.js0000644001412200141220000010545312265562324020765 0ustar benderbender /* * jQuery UI Datepicker 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Datepicker * * Depends: * jquery.ui.core.js */ (function(d,G){function K(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass= "ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su", "Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10", minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};d.extend(this._defaults,this.regional[""]);this.dpDiv=d('
      ')}function E(a,b){d.extend(a,b);for(var c in b)if(b[c]== null||b[c]==G)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.7"}});var y=(new Date).getTime();d.extend(K.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){E(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=f}}}e=a.nodeName.toLowerCase(); f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:d('
      ')}}, _connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&& b.append.remove();if(c){b.append=d(''+c+"");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('').addClass(this._triggerClass).html(f== ""?c:d("").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;gh){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a, c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b), true);this._updateDatepicker(b);this._updateAlternate(b);b.dpDiv.show()}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}E(a.settings,e||{}); b=b&&b.constructor==Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass); this._showDatepicker(this._dialogInput[0]);d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup", this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs, function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null: f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;for(var b=0;b-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true}, _showDatepicker:function(a){a=a.target||a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);d.datepicker._curInst&&d.datepicker._curInst!=b&&d.datepicker._curInst.dpDiv.stop(true,true);var c=d.datepicker._get(b,"beforeShow");E(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos= d.datepicker._findPos(a);d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.empty();b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b, c,e);b.dpDiv.css({position:d.datepicker._inDialog&&d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){d.datepicker._datepickerShowing=true;var i=b.dpDiv.find("iframe.ui-datepicker-cover");if(i.length){var g=d.datepicker._getBorders(b.dpDiv);i.css({left:-g[0],top:-g[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex(d(a).zIndex()+1);d.effects&& d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this,c=d.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a));var e=a.dpDiv.find("iframe.ui-datepicker-cover");e.length&&e.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()});a.dpDiv.find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout", function(){d(this).removeClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!b._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){d(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");d(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!= -1&&d(this).addClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).addClass("ui-datepicker-next-hover")}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();c=this._getNumberOfMonths(a);e=c[1];e>1?a.dpDiv.addClass("ui-datepicker-multi-"+e).css("width",17*e+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(c[0]!=1||c[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a, "isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input.focus();if(a.yearshtml){var f=a.yearshtml;setTimeout(function(){f===a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml);f=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]}, _checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),j=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e- g):0);b.top-=Math.min(b.top,b.top+f>j&&j>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1);)a=a[b?"previousSibling":"nextSibling"];a=d(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]? b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();if(a=this._get(b,"onClose"))a.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")}, _checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"): 0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear= false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var b=this._getInst(d(a)[0]);b.input&&b._selectingMonthYear&&setTimeout(function(){b.input.focus()},0);b._selectingMonthYear=!b._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay= d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=d(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a); else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b= a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;for(var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff,f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames, j=c=-1,l=-1,u=-1,k=false,o=function(p){(p=z+1-1){j=1;l=u;do{e=this._getDaysInMonth(c,j-1);if(l<=e)break;j++;l-=e}while(1)}w=this._daylightSavingAdjust(new Date(c,j-1,l));if(w.getFullYear()!=c||w.getMonth()+1!=j||w.getDate()!=l)throw"Invalid date";return w},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y", RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=k+112?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay= a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(), b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),j=this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),k=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n= this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=k&&nn;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-j,1)),this._getFormatConfig(a));n=this._canAdjustMonth(a,-1,m,g)?''+n+"":f?"":''+n+"";var r=this._get(a,"nextText");r=!h?r:this.formatDate(r,this._daylightSavingAdjust(new Date(m,g+j,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?''+r+"":f?"":''+r+"";j=this._get(a,"currentText");r=this._get(a,"gotoCurrent")&&a.currentDay?u:b;j=!h?j:this.formatDate(j,r,this._getFormatConfig(a));h=!a.inline?'":"";e=e?'
      '+(c?h:"")+(this._isInRange(a,r)?'":"")+(c?"":h)+"
      ":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;j=this._get(a,"showWeek");r=this._get(a,"dayNames");this._get(a,"dayNamesShort");var s=this._get(a,"dayNamesMin"),z= this._get(a,"monthNames"),w=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),v=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var L=this._getDefaultDate(a),I="",C=0;C1)switch(D){case 0:x+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]- 1:x+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:x+=" ui-datepicker-group-middle";t="";break}x+='">'}x+='
      '+(/all|left/.test(t)&&C==0?c?f:n:"")+(/all|right/.test(t)&&C==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,k,o,C>0||D>0,z,w)+'
      ';var A=j?'":"";for(t=0;t<7;t++){var q= (t+h)%7;A+="=5?' class="ui-datepicker-week-end"':"")+'>'+s[q]+""}x+=A+"";A=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay,A);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;A=l?6:Math.ceil((t+A)/7);q=this._daylightSavingAdjust(new Date(m,g,1-t));for(var O=0;O";var P=!j?"":'";for(t=0;t<7;t++){var F= p?p.apply(a.input?a.input[0]:null,[q]):[true,""],B=q.getMonth()!=g,J=B&&!H||!F[0]||k&&qo;P+='";q.setDate(q.getDate()+1);q=this._daylightSavingAdjust(q)}x+= P+""}g++;if(g>11){g=0;m++}x+="
      '+this._get(a,"weekHeader")+"
      '+this._get(a,"calculateWeek")(q)+""+(B&&!v?" ":J?''+q.getDate()+"":''+q.getDate()+"")+"
      "+(l?""+(i[0]>0&&D==i[1]-1?'
      ':""):"");M+=x}I+=M}I+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'':"");a._keyEvent=false;return I},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var j=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),k='
      ', o="";if(h||!j)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(k+=o+(h||!(j&& l)?" ":""));a.yearshtml="";if(h||!l)k+=''+c+"";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b,i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(a.yearshtml+='";if(d.browser.mozilla)k+='";else{k+=a.yearshtml;a.yearshtml=null}}k+=this._get(a,"yearSuffix");if(u)k+=(h||!(j&&l)?" ":"")+o;k+="
      ";return k},_adjustInstDate:function(a,b,c){var e= a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a, "onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a); c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a, "dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker= function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b)); return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new K;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.7";window["DP_jQuery_"+y]=d})(jQuery); drupal-7.26/misc/ui/jquery.ui.dialog.css0000644001412200141220000000252312265562324017475 0ustar benderbender /* * jQuery UI Dialog 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Dialog#theming */ .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } .ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } .ui-draggable .ui-dialog-titlebar { cursor: move; } drupal-7.26/misc/ui/jquery.effects.core.min.js0000644001412200141220000002511512265562324020600 0ustar benderbender /* * jQuery UI Effects 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/ */ jQuery.effects||function(f,j){function n(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1], 16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return o.transparent;return o[f.trim(c).toLowerCase()]}function s(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return n(b)}function p(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle, a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function q(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in t||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function u(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d= a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:b in f.fx.speeds?f.fx.speeds[b]:f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}function m(c){if(!c||typeof c==="number"||f.fx.speeds[c])return true;if(typeof c==="string"&&!f.effects[c])return true;return false}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor", "borderTopColor","borderColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=s(b.elem,a);b.end=n(b.end);b.colorInit=true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var o={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0, 0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211, 211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},r=["add","remove","toggle"],t={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b, d){if(f.isFunction(b)){d=b;b=null}return this.each(function(){f.queue(this,"fx",function(){var e=f(this),g=e.attr("style")||" ",h=q(p.call(this)),l,v=e.attr("className");f.each(r,function(w,i){c[i]&&e[i+"Class"](c[i])});l=q(p.call(this));e.attr("className",v);e.animate(u(h,l),a,b,function(){f.each(r,function(w,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments)});h=f.queue(this);l= h.splice(h.length-1,1)[0];h.splice(1,0,l);f.dequeue(this)})})};f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c}, b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this,[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.7",save:function(c,a){for(var b=0;b").addClass("ui-effects-wrapper").css({fontSize:"100%", background:"transparent",border:"none",margin:0,padding:0});c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});c.css({position:"relative",top:0,left:0})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c); return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments),b={options:a[1],duration:a[2],callback:a[3]};a=b.options.mode;var d=f.effects[c];if(f.fx.off||!d)return a?this[a](b.duration,b.callback):this.each(function(){b.callback&&b.callback.call(this)});return d.call(this,b)},_show:f.fn.show,show:function(c){if(m(c))return this._show.apply(this,arguments); else{var a=k.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(m(c))return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(m(c)||typeof c==="boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c), b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c, a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c, a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a== e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h");if(!a.values)a.values=[this._valueMin(),this._valueMin()];if(a.values.length&&a.values.length!==2)a.values=[a.values[0],a.values[0]]}else this.range=d("
      ");this.range.appendTo(this.element).addClass("ui-slider-range");if(a.range==="min"||a.range==="max")this.range.addClass("ui-slider-range-"+a.range);this.range.addClass("ui-widget-header")}d(".ui-slider-handle",this.element).length===0&&d("").appendTo(this.element).addClass("ui-slider-handle"); if(a.values&&a.values.length)for(;d(".ui-slider-handle",this.element).length").appendTo(this.element).addClass("ui-slider-handle");this.handles=d(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(c){c.preventDefault()}).hover(function(){a.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(a.disabled)d(this).blur(); else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(c){d(this).data("index.ui-slider-handle",c)});this.handles.keydown(function(c){var e=true,f=d(this).data("index.ui-slider-handle"),h,g,i;if(!b.options.disabled){switch(c.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:e= false;if(!b._keySliding){b._keySliding=true;d(this).addClass("ui-state-active");h=b._start(c,f);if(h===false)return}break}i=b.options.step;h=b.options.values&&b.options.values.length?(g=b.values(f)):(g=b.value());switch(c.keyCode){case d.ui.keyCode.HOME:g=b._valueMin();break;case d.ui.keyCode.END:g=b._valueMax();break;case d.ui.keyCode.PAGE_UP:g=b._trimAlignValue(h+(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:g=b._trimAlignValue(h-(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(h=== b._valueMax())return;g=b._trimAlignValue(h+i);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(h===b._valueMin())return;g=b._trimAlignValue(h-i);break}b._slide(c,f,g);return e}}).keyup(function(c){var e=d(this).data("index.ui-slider-handle");if(b._keySliding){b._keySliding=false;b._stop(c,e);b._change(c,e);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"); this._mouseDestroy();return this},_mouseCapture:function(b){var a=this.options,c,e,f,h,g;if(a.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:b.pageX,y:b.pageY});e=this._valueMax()-this._valueMin()+1;h=this;this.handles.each(function(i){var j=Math.abs(c-h.values(i));if(e>j){e=j;f=d(this);g=i}});if(a.range===true&&this.values(1)===a.min){g+=1;f=d(this.handles[g])}if(this._start(b, g)===false)return false;this._mouseSliding=true;h._handleIndex=g;f.addClass("ui-state-active").focus();a=f.offset();this._clickOffset=!d(b.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:b.pageX-a.left-f.width()/2,top:b.pageY-a.top-f.height()/2-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(b,g,c);return this._animateOff=true},_mouseStart:function(){return true}, _mouseDrag:function(b){var a=this._normValueFromMouse({x:b.pageX,y:b.pageY});this._slide(b,this._handleIndex,a);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(b){var a; if(this.orientation==="horizontal"){a=this.elementSize.width;b=b.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{a=this.elementSize.height;b=b.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}a=b/a;if(a>1)a=1;if(a<0)a=0;if(this.orientation==="vertical")a=1-a;b=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+a*b)},_start:function(b,a){var c={handle:this.handles[a],value:this.value()};if(this.options.values&&this.options.values.length){c.value= this.values(a);c.values=this.values()}return this._trigger("start",b,c)},_slide:function(b,a,c){var e;if(this.options.values&&this.options.values.length){e=this.values(a?0:1);if(this.options.values.length===2&&this.options.range===true&&(a===0&&c>e||a===1&&c1){this.options.values[b]=this._trimAlignValue(a);this._refreshValue();this._change(null,b)}if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;e=arguments[0];for(f=0;f=this._valueMax())return this._valueMax();var a=this.options.step>0?this.options.step:1,c=(b-this._valueMin())%a;alignValue=b-c;if(Math.abs(c)*2>=a)alignValue+=c>0?a:-a;return parseFloat(alignValue.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max}, _refreshValue:function(){var b=this.options.range,a=this.options,c=this,e=!this._animateOff?a.animate:false,f,h={},g,i,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(k){f=(c.values(k)-c._valueMin())/(c._valueMax()-c._valueMin())*100;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";d(this).stop(1,1)[e?"animate":"css"](h,a.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(k===0)c.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},a.animate); if(k===1)c.range[e?"animate":"css"]({width:f-g+"%"},{queue:false,duration:a.animate})}else{if(k===0)c.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},a.animate);if(k===1)c.range[e?"animate":"css"]({height:f-g+"%"},{queue:false,duration:a.animate})}g=f});else{i=this.value();j=this._valueMin();l=this._valueMax();f=l!==j?(i-j)/(l-j)*100:0;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";this.handle.stop(1,1)[e?"animate":"css"](h,a.animate);if(b==="min"&&this.orientation==="horizontal")this.range.stop(1, 1)[e?"animate":"css"]({width:f+"%"},a.animate);if(b==="max"&&this.orientation==="horizontal")this.range[e?"animate":"css"]({width:100-f+"%"},{queue:false,duration:a.animate});if(b==="min"&&this.orientation==="vertical")this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},a.animate);if(b==="max"&&this.orientation==="vertical")this.range[e?"animate":"css"]({height:100-f+"%"},{queue:false,duration:a.animate})}}});d.extend(d.ui.slider,{version:"1.8.7"})})(jQuery); drupal-7.26/misc/ui/jquery.ui.selectable.min.js0000644001412200141220000001032112265562324020742 0ustar benderbender /* * jQuery UI Selectable 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Selectables * * Depends: * jquery.ui.core.js * jquery.ui.mouse.js * jquery.ui.widget.js */ (function(e){e.widget("ui.selectable",e.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var c=this;this.element.addClass("ui-selectable");this.dragged=false;var f;this.refresh=function(){f=e(c.options.filter,c.element[0]);f.each(function(){var d=e(this),b=d.offset();e.data(this,"selectable-item",{element:this,$element:d,left:b.left,top:b.top,right:b.left+d.outerWidth(),bottom:b.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"), selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=f.addClass("ui-selectee");this._mouseInit();this.helper=e("
      ")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(c){var f=this;this.opos=[c.pageX, c.pageY];if(!this.options.disabled){var d=this.options;this.selectees=e(d.filter,this.element[0]);this._trigger("start",c);e(d.appendTo).append(this.helper);this.helper.css({left:c.clientX,top:c.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var b=e.data(this,"selectable-item");b.startselected=true;if(!c.metaKey){b.$element.removeClass("ui-selected");b.selected=false;b.$element.addClass("ui-unselecting");b.unselecting=true;f._trigger("unselecting", c,{unselecting:b.element})}});e(c.target).parents().andSelf().each(function(){var b=e.data(this,"selectable-item");if(b){var g=!c.metaKey||!b.$element.hasClass("ui-selected");b.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");b.unselecting=!g;b.selecting=g;(b.selected=g)?f._trigger("selecting",c,{selecting:b.element}):f._trigger("unselecting",c,{unselecting:b.element});return false}})}},_mouseDrag:function(c){var f=this;this.dragged=true;if(!this.options.disabled){var d= this.options,b=this.opos[0],g=this.opos[1],h=c.pageX,i=c.pageY;if(b>h){var j=h;h=b;b=j}if(g>i){j=i;i=g;g=j}this.helper.css({left:b,top:g,width:h-b,height:i-g});this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!(!a||a.element==f.element[0])){var k=false;if(d.tolerance=="touch")k=!(a.left>h||a.righti||a.bottomb&&a.rightg&&a.bottom=0)&&c(a).is(":focusable")}}); c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(), top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle= this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne", nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor== String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),k=0;k=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,k);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection(); this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){e(this).removeClass("ui-resizable-autohide");b._handles.show()},function(){if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()}; if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(), d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"});this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset= this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio: this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis];if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize", b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height; f={width:c.size.width-(f?0:c.sizeDiff.width),height:c.size.height-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f,{top:g,left:d}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop", b);this._helper&&this.helper.remove();return false},_updateCache:function(b){this.offset=this.helper.offset();if(l(b.left))this.position.left=b.left;if(l(b.top))this.position.top=b.top;if(l(b.height))this.size.height=b.height;if(l(b.width))this.size.width=b.width},_updateRatio:function(b){var a=this.position,c=this.size,d=this.axis;if(b.height)b.width=c.height*this.aspectRatio;else if(b.width)b.height=c.width/this.aspectRatio;if(d=="sw"){b.left=a.left+(c.width-b.width);b.top=null}if(d=="nw"){b.top= a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this.options,c=this.axis,d=l(b.width)&&a.maxWidth&&a.maxWidthb.width,h=l(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height, k=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&k)b.left=i-a.minWidth;if(d&&k)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+ a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this, arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]);b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});e.extend(e.ui.resizable, {version:"1.8.7"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(),10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize, function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top-f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var k=e(this),q=e(this).data("resizable-alsoresize"),p={},r=j&&j.length?j:k.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(r,function(n,o){if((n= (q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(k.css("position"))){c._revertToRelativePosition=true;k.css({position:"absolute",top:"auto",left:"auto"})}k.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType?e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})};if(b._revertToRelativePosition){b._revertToRelativePosition= false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a=e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-g};g=parseInt(a.element.css("left"),10)+(a.position.left- a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing,step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize", b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement=e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d=e(a),f=[];e(["Top", "Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset;var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options,d=a.containerOffset, f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left:a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?d.top:0}a.offset.left= a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top-d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(d+ a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition,f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&&/static/.test(f.css("position"))&& e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25,display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable");b.ghost&&b.ghost.css({position:"relative", height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b=e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width= d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,10)||0},l=function(b){return!isNaN(parseInt(b,10))}})(jQuery); drupal-7.26/misc/ui/jquery.ui.position.min.js0000644001412200141220000000703512265562324020513 0ustar benderbender /* * jQuery UI Position 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Position */ (function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY, left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+= k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+parseInt(c.curCSS(this,"marginRight",true))||0,w=m+q+parseInt(c.curCSS(this,"marginBottom",true))||0,i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-=m/2; i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left= d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); drupal-7.26/misc/ui/jquery.effects.slide.min.js0000644001412200141220000000204612265562324020746 0ustar benderbender /* * jQuery UI Effects Slide 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/Slide * * Depends: * jquery.effects.core.js */ (function(c){c.effects.slide=function(d){return this.queue(function(){var a=c(this),h=["position","top","left"],f=c.effects.setMode(a,d.options.mode||"show"),b=d.options.direction||"left";c.effects.save(a,h);a.show();c.effects.createWrapper(a).css({overflow:"hidden"});var g=b=="up"||b=="down"?"top":"left";b=b=="up"||b=="left"?"pos":"neg";var e=d.options.distance||(g=="top"?a.outerHeight({margin:true}):a.outerWidth({margin:true}));if(f=="show")a.css(g,b=="pos"?isNaN(e)?"-"+e:-e:e);var i={};i[g]=(f== "show"?b=="pos"?"+=":"-=":b=="pos"?"-=":"+=")+e;a.animate(i,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){f=="hide"&&a.hide();c.effects.restore(a,h);c.effects.removeWrapper(a);d.callback&&d.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery); drupal-7.26/misc/ui/jquery.effects.blind.min.js0000644001412200141220000000154712265562324020743 0ustar benderbender /* * jQuery UI Effects Blind 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/Blind * * Depends: * jquery.effects.core.js */ (function(b){b.effects.blind=function(c){return this.queue(function(){var a=b(this),g=["position","top","left"],f=b.effects.setMode(a,c.options.mode||"hide"),d=c.options.direction||"vertical";b.effects.save(a,g);a.show();var e=b.effects.createWrapper(a).css({overflow:"hidden"}),h=d=="vertical"?"height":"width";d=d=="vertical"?e.height():e.width();f=="show"&&e.css(h,0);var i={};i[h]=f=="show"?d:0;e.animate(i,c.duration,c.options.easing,function(){f=="hide"&&a.hide();b.effects.restore(a,g);b.effects.removeWrapper(a); c.callback&&c.callback.apply(a[0],arguments);a.dequeue()})})}})(jQuery); drupal-7.26/misc/ui/jquery.ui.selectable.css0000644001412200141220000000050312265562324020335 0ustar benderbender /* * jQuery UI Selectable 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Selectable#theming */ .ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } drupal-7.26/misc/ui/jquery.effects.explode.min.js0000644001412200141220000000315412265562324021307 0ustar benderbender /* * jQuery UI Effects Explode 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/Explode * * Depends: * jquery.effects.core.js */ (function(j){j.effects.explode=function(a){return this.queue(function(){var c=a.options.pieces?Math.round(Math.sqrt(a.options.pieces)):3,d=a.options.pieces?Math.round(Math.sqrt(a.options.pieces)):3;a.options.mode=a.options.mode=="toggle"?j(this).is(":visible")?"hide":"show":a.options.mode;var b=j(this).show().css("visibility","hidden"),g=b.offset();g.top-=parseInt(b.css("marginTop"),10)||0;g.left-=parseInt(b.css("marginLeft"),10)||0;for(var h=b.outerWidth(true),i=b.outerHeight(true),e=0;e").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+ e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery); drupal-7.26/misc/ui/jquery.effects.fold.min.js0000644001412200141220000000215112265562324020567 0ustar benderbender /* * jQuery UI Effects Fold 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/Fold * * Depends: * jquery.effects.core.js */ (function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","left"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1],10)/100* f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery); drupal-7.26/misc/ui/jquery.effects.transfer.min.js0000644001412200141220000000146012265562324021471 0ustar benderbender /* * jQuery UI Effects Transfer 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Effects/Transfer * * Depends: * jquery.effects.core.js */ (function(e){e.effects.transfer=function(a){return this.queue(function(){var b=e(this),c=e(a.options.to),d=c.offset();c={top:d.top,left:d.left,height:c.innerHeight(),width:c.innerWidth()};d=b.offset();var f=e('
      ').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments); b.dequeue()})})}})(jQuery); drupal-7.26/misc/ui/jquery.ui.theme.css0000644001412200141220000004530712265562324017347 0ustar benderbender /* * jQuery UI CSS Framework 1.8.7 * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Theming/API * * To view and modify this theme, visit http://jqueryui.com/themeroller/ */ /* Component containers ----------------------------------*/ .ui-widget { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1.1em/*{fsDefault}*/; } .ui-widget .ui-widget { font-size: 1em; } .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1em; } .ui-widget-content { border: 1px solid #aaaaaa/*{borderColorContent}*/; background: #ffffff/*{bgColorContent}*/ url(images/ui-bg_flat_75_ffffff_40x100.png)/*{bgImgUrlContent}*/ 50%/*{bgContentXPos}*/ 50%/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/; color: #222222/*{fcContent}*/; } .ui-widget-content a { color: #222222/*{fcContent}*/; } .ui-widget-header { border: 1px solid #aaaaaa/*{borderColorHeader}*/; background: #cccccc/*{bgColorHeader}*/ url(images/ui-bg_highlight-soft_75_cccccc_1x100.png)/*{bgImgUrlHeader}*/ 50%/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/; color: #222222/*{fcHeader}*/; font-weight: bold; } .ui-widget-header a { color: #222222/*{fcHeader}*/; } /* Interaction states ----------------------------------*/ .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; } .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555/*{fcDefault}*/; text-decoration: none; } .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999/*{borderColorHover}*/; background: #dadada/*{bgColorHover}*/ url(images/ui-bg_glass_75_dadada_1x400.png)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; } .ui-state-hover a, .ui-state-hover a:hover { color: #212121/*{fcHover}*/; text-decoration: none; } .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; } .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121/*{fcActive}*/; text-decoration: none; } .ui-widget :active { outline: none; } /* Interaction Cues ----------------------------------*/ .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; } .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636/*{fcHighlight}*/; } .ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_glass_95_fef1ec_1x400.png)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; } .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a/*{fcError}*/; } .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a/*{fcError}*/; } .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } .ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } .ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } /* Icons ----------------------------------*/ /* states and images */ .ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } .ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } .ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHeader}*/; } .ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/; } .ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHover}*/; } .ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/; } .ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/; } .ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/; } /* positioning */ .ui-icon-carat-1-n { background-position: 0 0; } .ui-icon-carat-1-ne { background-position: -16px 0; } .ui-icon-carat-1-e { background-position: -32px 0; } .ui-icon-carat-1-se { background-position: -48px 0; } .ui-icon-carat-1-s { background-position: -64px 0; } .ui-icon-carat-1-sw { background-position: -80px 0; } .ui-icon-carat-1-w { background-position: -96px 0; } .ui-icon-carat-1-nw { background-position: -112px 0; } .ui-icon-carat-2-n-s { background-position: -128px 0; } .ui-icon-carat-2-e-w { background-position: -144px 0; } .ui-icon-triangle-1-n { background-position: 0 -16px; } .ui-icon-triangle-1-ne { background-position: -16px -16px; } .ui-icon-triangle-1-e { background-position: -32px -16px; } .ui-icon-triangle-1-se { background-position: -48px -16px; } .ui-icon-triangle-1-s { background-position: -64px -16px; } .ui-icon-triangle-1-sw { background-position: -80px -16px; } .ui-icon-triangle-1-w { background-position: -96px -16px; } .ui-icon-triangle-1-nw { background-position: -112px -16px; } .ui-icon-triangle-2-n-s { background-position: -128px -16px; } .ui-icon-triangle-2-e-w { background-position: -144px -16px; } .ui-icon-arrow-1-n { background-position: 0 -32px; } .ui-icon-arrow-1-ne { background-position: -16px -32px; } .ui-icon-arrow-1-e { background-position: -32px -32px; } .ui-icon-arrow-1-se { background-position: -48px -32px; } .ui-icon-arrow-1-s { background-position: -64px -32px; } .ui-icon-arrow-1-sw { background-position: -80px -32px; } .ui-icon-arrow-1-w { background-position: -96px -32px; } .ui-icon-arrow-1-nw { background-position: -112px -32px; } .ui-icon-arrow-2-n-s { background-position: -128px -32px; } .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } .ui-icon-arrow-2-e-w { background-position: -160px -32px; } .ui-icon-arrow-2-se-nw { background-position: -176px -32px; } .ui-icon-arrowstop-1-n { background-position: -192px -32px; } .ui-icon-arrowstop-1-e { background-position: -208px -32px; } .ui-icon-arrowstop-1-s { background-position: -224px -32px; } .ui-icon-arrowstop-1-w { background-position: -240px -32px; } .ui-icon-arrowthick-1-n { background-position: 0 -48px; } .ui-icon-arrowthick-1-ne { background-position: -16px -48px; } .ui-icon-arrowthick-1-e { background-position: -32px -48px; } .ui-icon-arrowthick-1-se { background-position: -48px -48px; } .ui-icon-arrowthick-1-s { background-position: -64px -48px; } .ui-icon-arrowthick-1-sw { background-position: -80px -48px; } .ui-icon-arrowthick-1-w { background-position: -96px -48px; } .ui-icon-arrowthick-1-nw { background-position: -112px -48px; } .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } .ui-icon-arrowreturn-1-w { background-position: -64px -64px; } .ui-icon-arrowreturn-1-n { background-position: -80px -64px; } .ui-icon-arrowreturn-1-e { background-position: -96px -64px; } .ui-icon-arrowreturn-1-s { background-position: -112px -64px; } .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } .ui-icon-arrow-4 { background-position: 0 -80px; } .ui-icon-arrow-4-diag { background-position: -16px -80px; } .ui-icon-extlink { background-position: -32px -80px; } .ui-icon-newwin { background-position: -48px -80px; } .ui-icon-refresh { background-position: -64px -80px; } .ui-icon-shuffle { background-position: -80px -80px; } .ui-icon-transfer-e-w { background-position: -96px -80px; } .ui-icon-transferthick-e-w { background-position: -112px -80px; } .ui-icon-folder-collapsed { background-position: 0 -96px; } .ui-icon-folder-open { background-position: -16px -96px; } .ui-icon-document { background-position: -32px -96px; } .ui-icon-document-b { background-position: -48px -96px; } .ui-icon-note { background-position: -64px -96px; } .ui-icon-mail-closed { background-position: -80px -96px; } .ui-icon-mail-open { background-position: -96px -96px; } .ui-icon-suitcase { background-position: -112px -96px; } .ui-icon-comment { background-position: -128px -96px; } .ui-icon-person { background-position: -144px -96px; } .ui-icon-print { background-position: -160px -96px; } .ui-icon-trash { background-position: -176px -96px; } .ui-icon-locked { background-position: -192px -96px; } .ui-icon-unlocked { background-position: -208px -96px; } .ui-icon-bookmark { background-position: -224px -96px; } .ui-icon-tag { background-position: -240px -96px; } .ui-icon-home { background-position: 0 -112px; } .ui-icon-flag { background-position: -16px -112px; } .ui-icon-calendar { background-position: -32px -112px; } .ui-icon-cart { background-position: -48px -112px; } .ui-icon-pencil { background-position: -64px -112px; } .ui-icon-clock { background-position: -80px -112px; } .ui-icon-disk { background-position: -96px -112px; } .ui-icon-calculator { background-position: -112px -112px; } .ui-icon-zoomin { background-position: -128px -112px; } .ui-icon-zoomout { background-position: -144px -112px; } .ui-icon-search { background-position: -160px -112px; } .ui-icon-wrench { background-position: -176px -112px; } .ui-icon-gear { background-position: -192px -112px; } .ui-icon-heart { background-position: -208px -112px; } .ui-icon-star { background-position: -224px -112px; } .ui-icon-link { background-position: -240px -112px; } .ui-icon-cancel { background-position: 0 -128px; } .ui-icon-plus { background-position: -16px -128px; } .ui-icon-plusthick { background-position: -32px -128px; } .ui-icon-minus { background-position: -48px -128px; } .ui-icon-minusthick { background-position: -64px -128px; } .ui-icon-close { background-position: -80px -128px; } .ui-icon-closethick { background-position: -96px -128px; } .ui-icon-key { background-position: -112px -128px; } .ui-icon-lightbulb { background-position: -128px -128px; } .ui-icon-scissors { background-position: -144px -128px; } .ui-icon-clipboard { background-position: -160px -128px; } .ui-icon-copy { background-position: -176px -128px; } .ui-icon-contact { background-position: -192px -128px; } .ui-icon-image { background-position: -208px -128px; } .ui-icon-video { background-position: -224px -128px; } .ui-icon-script { background-position: -240px -128px; } .ui-icon-alert { background-position: 0 -144px; } .ui-icon-info { background-position: -16px -144px; } .ui-icon-notice { background-position: -32px -144px; } .ui-icon-help { background-position: -48px -144px; } .ui-icon-check { background-position: -64px -144px; } .ui-icon-bullet { background-position: -80px -144px; } .ui-icon-radio-off { background-position: -96px -144px; } .ui-icon-radio-on { background-position: -112px -144px; } .ui-icon-pin-w { background-position: -128px -144px; } .ui-icon-pin-s { background-position: -144px -144px; } .ui-icon-play { background-position: 0 -160px; } .ui-icon-pause { background-position: -16px -160px; } .ui-icon-seek-next { background-position: -32px -160px; } .ui-icon-seek-prev { background-position: -48px -160px; } .ui-icon-seek-end { background-position: -64px -160px; } .ui-icon-seek-start { background-position: -80px -160px; } /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ .ui-icon-seek-first { background-position: -80px -160px; } .ui-icon-stop { background-position: -96px -160px; } .ui-icon-eject { background-position: -112px -160px; } .ui-icon-volume-off { background-position: -128px -160px; } .ui-icon-volume-on { background-position: -144px -160px; } .ui-icon-power { background-position: 0 -176px; } .ui-icon-signal-diag { background-position: -16px -176px; } .ui-icon-signal { background-position: -32px -176px; } .ui-icon-battery-0 { background-position: -48px -176px; } .ui-icon-battery-1 { background-position: -64px -176px; } .ui-icon-battery-2 { background-position: -80px -176px; } .ui-icon-battery-3 { background-position: -96px -176px; } .ui-icon-circle-plus { background-position: 0 -192px; } .ui-icon-circle-minus { background-position: -16px -192px; } .ui-icon-circle-close { background-position: -32px -192px; } .ui-icon-circle-triangle-e { background-position: -48px -192px; } .ui-icon-circle-triangle-s { background-position: -64px -192px; } .ui-icon-circle-triangle-w { background-position: -80px -192px; } .ui-icon-circle-triangle-n { background-position: -96px -192px; } .ui-icon-circle-arrow-e { background-position: -112px -192px; } .ui-icon-circle-arrow-s { background-position: -128px -192px; } .ui-icon-circle-arrow-w { background-position: -144px -192px; } .ui-icon-circle-arrow-n { background-position: -160px -192px; } .ui-icon-circle-zoomin { background-position: -176px -192px; } .ui-icon-circle-zoomout { background-position: -192px -192px; } .ui-icon-circle-check { background-position: -208px -192px; } .ui-icon-circlesmall-plus { background-position: 0 -208px; } .ui-icon-circlesmall-minus { background-position: -16px -208px; } .ui-icon-circlesmall-close { background-position: -32px -208px; } .ui-icon-squaresmall-plus { background-position: -48px -208px; } .ui-icon-squaresmall-minus { background-position: -64px -208px; } .ui-icon-squaresmall-close { background-position: -80px -208px; } .ui-icon-grip-dotted-vertical { background-position: 0 -224px; } .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } .ui-icon-grip-solid-vertical { background-position: -32px -224px; } .ui-icon-grip-solid-horizontal { background-position: -48px -224px; } .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } .ui-icon-grip-diagonal-se { background-position: -80px -224px; } /* Misc visuals ----------------------------------*/ /* Corner radius */ .ui-corner-tl { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; } .ui-corner-tr { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; } .ui-corner-bl { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; } .ui-corner-br { -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; } .ui-corner-top { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; } .ui-corner-bottom { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; } .ui-corner-right { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; } .ui-corner-left { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; } .ui-corner-all { -moz-border-radius: 4px/*{cornerRadius}*/; -webkit-border-radius: 4px/*{cornerRadius}*/; border-radius: 4px/*{cornerRadius}*/; } /* Overlays */ .ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; } .ui-widget-shadow { margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/; padding: 8px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityShadow}*/; -moz-border-radius: 8px/*{cornerRadiusShadow}*/; -webkit-border-radius: 8px/*{cornerRadiusShadow}*/; border-radius: 8px/*{cornerRadiusShadow}*/; } drupal-7.26/misc/powered-gray-135x42.png0000644001412200141220000000504212265562324017133 0ustar benderbenderPNG  IHDR*PLTEOo"[~TW]Cg~te{']~^VqW~cnvgQ(sf[Sp}#\3|[i|QrRٚҋ̲'w:g)`U `A\Vey[l_ [rT]mUtc@ \plZ/wZb-z{]`)t7}5{i߀6gr2z"ozĈf~n\rSz| ^Jrf`Uye%kYx牽>o ^Yrrriiimmm}}}ŽruuuyyydddR]_dX~tc@˥慅 `8}mazۉZip\hZV&j2QgFt-;,joع`= X%qbbb4$ImGsBD"W`9oY`\k#b UIDATx^U00.3/33333kMv}#e 7^_zXEeTK(+JC[U )"ٵ=NtWj h/8aI$'ؖEAp5q:li~M!Ni ɰtiOG'`sR5G/3M Y@ M&wvA3m 2 }G4)Cc rv"Nlz0َ޾{X´N0mR| uGÃDK% ,*t4}bc eM WST6ck2~u?|6e*Ez U"KYY!rX)]kGD!Ћ/F0eq9; }k,Sy.xѲ|$)%{0O  7A.i<'R_,UJCOs_ߋb0.HcSH'S!}Ƀ4B)0i]t!ÐG`#齮b0ݻnڰhǀ`&~_,׳DCS;d=P\+ !#ҳq= M \vsMNwY_ ,ǻ}b%\\eQsk"DJIj2Nh01\n>tK:n|mY &%s nL&R5iԞNp#D k̻4Fz v1{5GkȼܩR=}0qT5QCs k;<5]2H'F~_L\Vv146GFFxpJhۋ{z[YR\B\|G=lssn |z=npU\|\!':"pV--56"PG,(@xMGDT@GGJ%'olLlnN2w/e2$%FeYIn/A&^BB׋lšƋO(>?T*ct4)+|^P jOB=~;4Kfgd49Y1-u̴<AЎ 4TR΂VL*h4vMDZLIIH mH}_vjŷjnIENDB`drupal-7.26/misc/powered-black-80x15.png0000644001412200141220000000265012265562324017166 0ustar benderbenderPNG  IHDRPDPLTE,$ahWYk&qb$m*2u)-r',p#*m #j hhfb ZD S [_ ZXSd[U)s!j!e^JimTϫڅ*6x$1t TT MQ]6|r)^J>W*;|#c [OVEJ|''\GGsootOԐ^k,9p=J}⻻;;p,r,?$7x+i(g.p2E{[[^''a,GA*C$={1q#d3Awޖ{tj-J0l (f %c#:q0hS`CU!j#oR-J)H&F$B4q2s4v2v -pTj&p$o#nz=K o\,L*K:w%Au.I{l={>>}=};|8z5x4v4w 2v .t -s$h"g*r)q&p,P)M'K!FDBA?=~:}8{8z 6y 4w/q0t.tl =,R)Q(N&M$J!H G HFEB@?= 9~ 6| 4y4z2y/x.w4z,U+U*T&P#N!LIHDB 9~8}2y>gAIDAT8Oh:M6O6W"RJѭ[;GCDhЩC!AR2""(9fsnޘ{͞mޯϧ(ί AaApؠO<:{xQI=IY!ׅ9:ZQ QGR5*C5o$ǎ'($s[:e^ǡ'ࡲvg.Y<^W$u.uJrM::W+Ͻٍ9 =/G4!k锫sʞOUq{OC{F&N6;,c3z6-T~6Ըfv;{5oTxjuvg50G]6mSi_sEWכ N o|o\hzqKqťTC{JPoTPЇ X{rdho*<|nh}릲TdƁﰗ *;"ݷܖrdgɑIM9Vogһ').appendTo(l)[0]);r.appendTo("body");r.data("form-plugin-onload",s);l.submit()}finally{l.setAttribute("action",m);o?l.setAttribute("target",o):i.removeAttr("target");b(v).remove()}}function s(){if(!G){r.removeData("form-plugin-onload");var o=true; try{if(F)throw"timeout";p=x.contentWindow?x.contentWindow.document:x.contentDocument?x.contentDocument:x.document;var m=e.dataType=="xml"||p.XMLDocument||b.isXMLDoc(p);q("isXml="+m);if(!m&&window.opera&&(p.body==null||p.body.innerHTML==""))if(--K){q("requeing onLoad callback, DOM not available");setTimeout(s,250);return}G=true;j.responseText=p.documentElement?p.documentElement.innerHTML:null;j.responseXML=p.XMLDocument?p.XMLDocument:p;j.getResponseHeader=function(L){return{"content-type":e.dataType}[L]}; var v=/(json|script)/.test(e.dataType);if(v||e.textarea){var w=p.getElementsByTagName("textarea")[0];if(w)j.responseText=w.value;else if(v){var H=p.getElementsByTagName("pre")[0],I=p.getElementsByTagName("body")[0];if(H)j.responseText=H.textContent;else if(I)j.responseText=I.innerHTML}}else if(e.dataType=="xml"&&!j.responseXML&&j.responseText!=null)j.responseXML=C(j.responseText);J=b.httpData(j,e.dataType)}catch(D){q("error caught:",D);o=false;j.error=D;b.handleError(e,j,"error",D)}if(j.aborted){q("upload aborted"); o=false}if(o){e.success.call(e.context,J,"success",j);y&&b.event.trigger("ajaxSuccess",[j,e])}y&&b.event.trigger("ajaxComplete",[j,e]);y&&!--b.active&&b.event.trigger("ajaxStop");if(e.complete)e.complete.call(e.context,j,o?"success":"error");setTimeout(function(){r.removeData("form-plugin-onload");r.remove();j.responseXML=null},100)}}function C(o,m){if(window.ActiveXObject){m=new ActiveXObject("Microsoft.XMLDOM");m.async="false";m.loadXML(o)}else m=(new DOMParser).parseFromString(o,"text/xml");return m&& m.documentElement&&m.documentElement.tagName!="parsererror"?m:null}var l=i[0];if(b(":input[name=submit],:input[id=submit]",l).length)alert('Error: Form elements must not have name or id of "submit".');else{var e=b.extend(true,{},b.ajaxSettings,a);e.context=e.context||e;var u="jqFormIO"+(new Date).getTime(),E="_"+u;window[E]=function(){var o=r.data("form-plugin-onload");if(o){o();window[E]=undefined;try{delete window[E]}catch(m){}}};var r=b(''; }; })(jQuery); drupal-7.26/modules/overlay/overlay.api.php0000644001412200141220000000204112265562324020310 0ustar benderbender' . t('About') . ''; $output .= '

      ' . t('The Toolbar module displays links to top-level administration menu items and links from other modules at the top of the screen. For more information, see the online handbook entry for Toolbar module.', array('@toolbar' => 'http://drupal.org/documentation/modules/toolbar/')) . '

      '; $output .= '

      ' . t('Uses') . '

      '; $output .= '
      '; $output .= '
      ' . t('Displaying administrative links') . '
      '; $output .= '
      ' . t('The Toolbar module displays a bar containing top-level administrative links across the top of the screen. Below that, the Toolbar module has a drawer section where it displays links provided by other modules, such as the core Shortcut module. The drawer can be hidden/shown by using the show/hide shortcuts link at the end of the toolbar.', array('@shortcuts-help' => url('admin/help/shortcut'))) . '
      '; $output .= '
      '; return $output; } } /** * Implements hook_permission(). */ function toolbar_permission() { return array( 'access toolbar' => array( 'title' => t('Use the administration toolbar'), ), ); } /** * Implements hook_theme(). */ function toolbar_theme($existing, $type, $theme, $path) { $items['toolbar'] = array( 'render element' => 'toolbar', 'template' => 'toolbar', 'path' => drupal_get_path('module', 'toolbar'), ); $items['toolbar_toggle'] = array( 'variables' => array( 'collapsed' => NULL, 'attributes' => array(), ), ); return $items; } /** * Implements hook_menu(). */ function toolbar_menu() { $items['toolbar/toggle'] = array( 'title' => 'Toggle drawer visibility', 'type' => MENU_CALLBACK, 'page callback' => 'toolbar_toggle_page', 'access arguments' => array('access toolbar'), ); return $items; } /** * Menu callback; toggles the visibility of the toolbar drawer. */ function toolbar_toggle_page() { global $base_path; // Toggle the value in the cookie. setcookie('Drupal.toolbar.collapsed', !_toolbar_is_collapsed(), NULL, $base_path); // Redirect the user from where he used the toggle element. drupal_goto(); } /** * Formats an element used to toggle the toolbar drawer's visibility. * * @param $variables * An associative array containing: * - collapsed: A boolean value representing the toolbar drawer's visibility. * - attributes: An associative array of HTML attributes. * * @return * An HTML string representing the element for toggling. * * @ingroup themable */ function theme_toolbar_toggle($variables) { if ($variables['collapsed']) { $toggle_text = t('Show shortcuts'); } else { $toggle_text = t('Hide shortcuts'); $variables['attributes']['class'][] = 'toggle-active'; } return l($toggle_text, 'toolbar/toggle', array('query' => drupal_get_destination(), 'attributes' => array('title' => $toggle_text) + $variables['attributes'])); } /** * Determines the current state of the toolbar drawer's visibility. * * @return * TRUE when drawer is collapsed, FALSE when it is expanded. */ function _toolbar_is_collapsed() { // PHP converts dots into underscores in cookie names to avoid problems with // its parser, so we use a converted cookie name. return isset($_COOKIE['Drupal_toolbar_collapsed']) ? $_COOKIE['Drupal_toolbar_collapsed'] : 0; } /** * Implements hook_page_build(). * * Add admin toolbar to the page_top region automatically. */ function toolbar_page_build(&$page) { $page['page_top']['toolbar'] = array( '#pre_render' => array('toolbar_pre_render'), '#access' => user_access('access toolbar'), 'toolbar_drawer' => array(), ); } /** * Prerender function for the toolbar. * * Since building the toolbar takes some time, it is done just prior to * rendering to ensure that it is built only if it will be displayed. */ function toolbar_pre_render($toolbar) { $toolbar = array_merge($toolbar, toolbar_view()); return $toolbar; } /** * Implements hook_preprocess_html(). * * Add some page classes, so global page theming can adjust to the toolbar. */ function toolbar_preprocess_html(&$vars) { if (isset($vars['page']['page_top']['toolbar']) && user_access('access toolbar')) { $vars['classes_array'][] = 'toolbar'; if (!_toolbar_is_collapsed()) { $vars['classes_array'][] = 'toolbar-drawer'; } } } /** * Implements hook_preprocess_toolbar(). * * Adding the 'overlay-displace-top' class to the toolbar pushes the overlay * down, so it appears below the toolbar. */ function toolbar_preprocess_toolbar(&$variables) { $variables['classes_array'][] = "overlay-displace-top"; } /** * Implements hook_system_info_alter(). * * Indicate that the 'page_top' region (in which the toolbar will be displayed) * is an overlay supplemental region that should be refreshed whenever its * content is updated. * * This information is provided for any module that might need to use it, not * just the core Overlay module. */ function toolbar_system_info_alter(&$info, $file, $type) { if ($type == 'theme') { $info['overlay_supplemental_regions'][] = 'page_top'; } } /** * Builds the admin menu as a structured array ready for drupal_render(). * * @return * Array of links and settings relating to the admin menu. */ function toolbar_view() { global $user; $module_path = drupal_get_path('module', 'toolbar'); $build = array( '#theme' => 'toolbar', '#attached'=> array( 'js' => array( $module_path . '/toolbar.js', array( 'data' => array('tableHeaderOffset' => 'Drupal.toolbar.height'), 'type' => 'setting' ), ), 'css' => array( $module_path . '/toolbar.css', ), 'library' => array(array('system', 'jquery.cookie')), ), ); // Retrieve the admin menu from the database. $links = toolbar_menu_navigation_links(toolbar_get_menu_tree()); $build['toolbar_menu'] = array( '#theme' => 'links__toolbar_menu', '#links' => $links, '#attributes' => array('id' => 'toolbar-menu'), '#heading' => array('text' => t('Administrative toolbar'), 'level' => 'h2', 'class' => 'element-invisible'), ); // Add logout & user account links or login link. if ($user->uid) { $links = array( 'account' => array( 'title' => t('Hello @username', array('@username' => format_username($user))), 'href' => 'user', 'html' => TRUE, 'attributes' => array('title' => t('User account')), ), 'logout' => array( 'title' => t('Log out'), 'href' => 'user/logout', ), ); } else { $links = array( 'login' => array( 'title' => t('Log in'), 'href' => 'user', ), ); } $build['toolbar_user'] = array( '#theme' => 'links__toolbar_user', '#links' => $links, '#attributes' => array('id' => 'toolbar-user'), ); // Add a "home" link. $link = array( 'home' => array( 'title' => 'Home', 'href' => '', 'html' => TRUE, 'attributes' => array('title' => t('Home')), ), ); $build['toolbar_home'] = array( '#theme' => 'links', '#links' => $link, '#attributes' => array('id' => 'toolbar-home'), ); // Add an anchor to be able to toggle the visibility of the drawer. $build['toolbar_toggle'] = array( '#theme' => 'toolbar_toggle', '#collapsed' => _toolbar_is_collapsed(), '#attributes' => array('class' => array('toggle')), ); // Prepare the drawer links CSS classes. $toolbar_drawer_classes = array( 'toolbar-drawer', 'clearfix', ); if(_toolbar_is_collapsed()) { $toolbar_drawer_classes[] = 'collapsed'; } $build['toolbar_drawer_classes'] = implode(' ', $toolbar_drawer_classes); return $build; } /** * Gets only the top level items below the 'admin' path. * * @return * An array containing a menu tree of top level items below the 'admin' path. */ function toolbar_get_menu_tree() { $tree = array(); $admin_link = db_query('SELECT * FROM {menu_links} WHERE menu_name = :menu_name AND module = :module AND link_path = :path', array(':menu_name' => 'management', ':module' => 'system', ':path' => 'admin'))->fetchAssoc(); if ($admin_link) { $tree = menu_build_tree('management', array( 'expanded' => array($admin_link['mlid']), 'min_depth' => $admin_link['depth'] + 1, 'max_depth' => $admin_link['depth'] + 1, )); } return $tree; } /** * Generates a links array from a menu tree array. * * Based on menu_navigation_links(). Adds path based IDs and icon placeholders * to the links. * * @return * An array of links as defined above. */ function toolbar_menu_navigation_links($tree) { $links = array(); foreach ($tree as $item) { if (!$item['link']['hidden'] && $item['link']['access']) { // Make sure we have a path specific ID in place, so we can attach icons // and behaviors to the items. $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['href']); $link = $item['link']['localized_options']; $link['href'] = $item['link']['href']; // Add icon placeholder. $link['title'] = '' . check_plain($item['link']['title']); // Add admin link ID. $link['attributes'] = array('id' => 'toolbar-link-' . $id); if (!empty($item['link']['description'])) { $link['title'] .= ' (' . $item['link']['description'] . ')'; $link['attributes']['title'] = $item['link']['description']; } $link['html'] = TRUE; $class = ' path-' . $id; if (toolbar_in_active_trail($item['link']['href'])) { $class .= ' active-trail'; } $links['menu-' . $item['link']['mlid'] . $class] = $link; } } return $links; } /** * Checks whether an item is in the active trail. * * Useful when using a menu generated by menu_tree_all_data() which does * not set the 'in_active_trail' flag on items. * * @return * TRUE when path is in the active trail, FALSE if not. * * @todo * Look at migrating to a menu system level function. */ function toolbar_in_active_trail($path) { $active_paths = &drupal_static(__FUNCTION__); // Gather active paths. if (!isset($active_paths)) { $active_paths = array(); $trail = menu_get_active_trail(); foreach ($trail as $item) { if (!empty($item['href'])) { $active_paths[] = $item['href']; } } } return in_array($path, $active_paths); } drupal-7.26/modules/toolbar/toolbar-rtl.css0000644001412200141220000000106112265562324020303 0ustar benderbender #toolbar, #toolbar * { text-align: right; } #toolbar ul li { float: right; } #toolbar ul li a { display: inline-block; float: none; zoom: 1; } #toolbar div.toolbar-menu { padding: 5px 50px 5px 50px; } #toolbar-user { float: left; } #toolbar ul#toolbar-user li { float: none; display: inline; } #toolbar-menu { float: none; } #toolbar-home { float: right; } #toolbar ul li.home a { position: absolute; right: 10px; } #toolbar div.toolbar-menu a.toggle { left: 10px; right: auto; } * html #toolbar { left: 0; padding-left: 0; } drupal-7.26/modules/toolbar/toolbar.css0000644001412200141220000000646012265562324017514 0ustar benderbender body.toolbar { padding-top: 2.2em; } body.toolbar-drawer { padding-top: 5.3em; } /** * Aggressive resets so we can achieve a consistent look in hostile CSS * environments. */ #toolbar, #toolbar * { border: 0; font-size: 100%; line-height: inherit; list-style: none; margin: 0; outline: 0; padding: 0; text-align: left; /* LTR */ vertical-align: baseline; } /** * Base styles. * * We use a keyword for the toolbar font size to make it display consistently * across different themes, while still allowing browsers to resize the text. */ #toolbar { background: #666; color: #ccc; font: normal small "Lucida Grande", Verdana, sans-serif; left: 0; margin: 0 -20px; padding: 0 20px; position: fixed; right: 0; top: 0; -moz-box-shadow: 0 3px 20px #000; -webkit-box-shadow: 0 3px 20px #000; box-shadow: 0 3px 20px #000; filter: progid:DXImageTransform.Microsoft.Shadow(color=#000000, direction='180', strength='10'); -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(color=#000000, direction='180', strength='10')"; z-index: 600; } #toolbar div.collapsed { display: none; visibility: hidden; } #toolbar a { color: #fff; font-size: .846em; text-decoration: none; } #toolbar ul li, #toolbar ul li a { float: left; /* LTR */ } /** * Administration menu. */ #toolbar div.toolbar-menu { background: #000; line-height: 20px; padding: 5px 50px 5px 10px; /* LTR */ position: relative; } #toolbar-home a span { background: url(toolbar.png) no-repeat 0 -45px; display: block; height: 14px; margin: 3px 0px; text-indent: -9999px; vertical-align: text-bottom; width: 11px; } #toolbar-user { float: right; /* LTR */ } #toolbar-menu { float: left; /* LTR */ } #toolbar div.toolbar-menu a.toggle { background: url(toolbar.png) 0 -20px no-repeat; bottom: 0; cursor: pointer; height: 25px; overflow: hidden; position: absolute; right: 10px; /* LTR */ text-indent: -9999px; width: 25px; } #toolbar div.toolbar-menu a.toggle:focus, #toolbar div.toolbar-menu a.toggle:hover { background-position: -50px -20px; } #toolbar div.toolbar-menu a.toggle-active { background-position: -25px -20px; } #toolbar div.toolbar-menu a.toggle-active.toggle:focus, #toolbar div.toolbar-menu a.toggle-active.toggle:hover { background-position: -75px -20px; } #toolbar div.toolbar-menu ul li a { padding: 0 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; } #toolbar div.toolbar-menu ul li a:focus, #toolbar div.toolbar-menu ul li a:hover, #toolbar div.toolbar-menu ul li a:active, #toolbar div.toolbar-menu ul li a.active:focus { background: #444; } #toolbar div.toolbar-menu ul li a.active:hover, #toolbar div.toolbar-menu ul li a.active:active, #toolbar div.toolbar-menu ul li a.active, #toolbar div.toolbar-menu ul li.active-trail a { background: url(toolbar.png) 0 0 repeat-x; text-shadow: #333 0 1px 0; } /** * Collapsed drawer of additional toolbar content. */ #toolbar div.toolbar-drawer { position: relative; padding: 0 10px; } /** * IE 6 Fix. * * IE 6 shows elements with position:fixed as position:static so we replace * it with position:absolute; toolbar needs its z-index to stay above overlay. */ * html #toolbar { left: -20px; margin: 0; padding-right: 0; position: absolute; right: 0; width: 100%; } drupal-7.26/modules/toolbar/toolbar.tpl.php0000644001412200141220000000247412265562324020312 0ustar benderbender
      drupal-7.26/modules/toolbar/toolbar.js0000644001412200141220000000571412265562324017341 0ustar benderbender(function ($) { Drupal.toolbar = Drupal.toolbar || {}; /** * Attach toggling behavior and notify the overlay of the toolbar. */ Drupal.behaviors.toolbar = { attach: function(context) { // Set the initial state of the toolbar. $('#toolbar', context).once('toolbar', Drupal.toolbar.init); // Toggling toolbar drawer. $('#toolbar a.toggle', context).once('toolbar-toggle').click(function(e) { Drupal.toolbar.toggle(); // Allow resize event handlers to recalculate sizes/positions. $(window).triggerHandler('resize'); return false; }); } }; /** * Retrieve last saved cookie settings and set up the initial toolbar state. */ Drupal.toolbar.init = function() { // Retrieve the collapsed status from a stored cookie. var collapsed = $.cookie('Drupal.toolbar.collapsed'); // Expand or collapse the toolbar based on the cookie value. if (collapsed == 1) { Drupal.toolbar.collapse(); } else { Drupal.toolbar.expand(); } }; /** * Collapse the toolbar. */ Drupal.toolbar.collapse = function() { var toggle_text = Drupal.t('Show shortcuts'); $('#toolbar div.toolbar-drawer').addClass('collapsed'); $('#toolbar a.toggle') .removeClass('toggle-active') .attr('title', toggle_text) .html(toggle_text); $('body').removeClass('toolbar-drawer').css('paddingTop', Drupal.toolbar.height()); $.cookie( 'Drupal.toolbar.collapsed', 1, { path: Drupal.settings.basePath, // The cookie should "never" expire. expires: 36500 } ); }; /** * Expand the toolbar. */ Drupal.toolbar.expand = function() { var toggle_text = Drupal.t('Hide shortcuts'); $('#toolbar div.toolbar-drawer').removeClass('collapsed'); $('#toolbar a.toggle') .addClass('toggle-active') .attr('title', toggle_text) .html(toggle_text); $('body').addClass('toolbar-drawer').css('paddingTop', Drupal.toolbar.height()); $.cookie( 'Drupal.toolbar.collapsed', 0, { path: Drupal.settings.basePath, // The cookie should "never" expire. expires: 36500 } ); }; /** * Toggle the toolbar. */ Drupal.toolbar.toggle = function() { if ($('#toolbar div.toolbar-drawer').hasClass('collapsed')) { Drupal.toolbar.expand(); } else { Drupal.toolbar.collapse(); } }; Drupal.toolbar.height = function() { var $toolbar = $('#toolbar'); var height = $toolbar.outerHeight(); // In modern browsers (including IE9), when box-shadow is defined, use the // normal height. var cssBoxShadowValue = $toolbar.css('box-shadow'); var boxShadow = (typeof cssBoxShadowValue !== 'undefined' && cssBoxShadowValue !== 'none'); // In IE8 and below, we use the shadow filter to apply box-shadow styles to // the toolbar. It adds some extra height that we need to remove. if (!boxShadow && /DXImageTransform\.Microsoft\.Shadow/.test($toolbar.css('filter'))) { height -= $toolbar[0].filters.item("DXImageTransform.Microsoft.Shadow").strength; } return height; }; })(jQuery); drupal-7.26/modules/toolbar/toolbar.png0000644001412200141220000000105612265562324017504 0ustar benderbenderPNG  IHDRd9,~OPLTEfffffffffffffff===>>>GGGHHHPPPQQQXXXfffstRNSO^zIDATx[O@o VK71i}.f.Ifbd`pg01\. \ԌP`MRhUl DB(gŰ8!+2VL,Z$MNY16:V,fKzANJZ?6 se]OԍI_"[Wߺ, }8D !yP,G9BUĩ%W'X"_U!N<*"IENDB`drupal-7.26/modules/contact/0000755001412200141220000000000012265562324015323 5ustar benderbenderdrupal-7.26/modules/contact/contact.install0000644001412200141220000001007112265562324020345 0ustar benderbender 'Contact form category settings.', 'fields' => array( 'cid' => array( 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Primary Key: Unique category ID.', ), 'category' => array( 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'Category name.', 'translatable' => TRUE, ), 'recipients' => array( 'type' => 'text', 'not null' => TRUE, 'size' => 'big', 'description' => 'Comma-separated list of recipient e-mail addresses.', ), 'reply' => array( 'type' => 'text', 'not null' => TRUE, 'size' => 'big', 'description' => 'Text of the auto-reply message.', ), 'weight' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => "The category's weight.", ), 'selected' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny', 'description' => 'Flag to indicate whether or not category is selected by default. (1 = Yes, 0 = No)', ), ), 'primary key' => array('cid'), 'unique keys' => array( 'category' => array('category'), ), 'indexes' => array( 'list' => array('weight', 'category'), ), ); return $schema; } /** * Implements hook_install(). */ function contact_install() { // Insert a default contact category. db_insert('contact') ->fields(array( 'category' => 'Website feedback', 'recipients' => variable_get('site_mail', ini_get('sendmail_from')), 'selected' => 1, 'reply' => '', )) ->execute(); } /** * Implements hook_uninstall(). */ function contact_uninstall() { variable_del('contact_default_status'); variable_del('contact_threshold_limit'); variable_del('contact_threshold_window'); } /** * Implements hook_update_dependencies(). */ function contact_update_dependencies() { // contact_update_7001() relies on the {role_permission} table being updated // to the new format and filled with data. $dependencies['contact'][7001] = array( 'system' => 7007, ); // contact_update_7002() relies on the {role_permission} table having the // module field, which is created in user_update_7006(). $dependencies['contact'][7002] = array( 'user' => 7006, ); return $dependencies; } /** * @addtogroup updates-6.x-to-7.x * @{ */ /** * Rename the threshold limit variable. */ function contact_update_7000() { variable_set('contact_threshold_limit', variable_get('contact_hourly_threshold', 5)); variable_del('contact_hourly_threshold'); } /** * Rename the administer contact forms permission. */ function contact_update_7001() { db_update('role_permission') ->fields(array('permission' => 'administer contact forms')) ->condition('permission', 'administer site-wide contact form') ->execute(); } /** * Enable the 'access user contact forms' for registered users by default. */ function contact_update_7002() { // Do not use user_role_grant_permission() since it relies on // hook_permission(), which will not run for contact module if it is // disabled. db_merge('role_permission') ->key(array( 'rid' => DRUPAL_AUTHENTICATED_RID, 'permission' => 'access user contact forms', 'module' => 'contact', )) ->execute(); } /** * Change the weight column to normal int. */ function contact_update_7003() { db_drop_index('contact', 'list'); db_change_field('contact', 'weight', 'weight', array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => "The category's weight.", ), array( 'indexes' => array( 'list' => array('weight', 'category'), ), )); } /** * @} End of "addtogroup updates-6.x-to-7.x". */ drupal-7.26/modules/contact/contact.admin.inc0000644001412200141220000001634612265562324020552 0ustar benderbender t('Operations'), 'colspan' => 2), ); $rows = array(); // Get all the contact categories from the database. $categories = db_select('contact', 'c') ->addTag('translatable') ->fields('c', array('cid', 'category', 'recipients', 'selected')) ->orderBy('weight') ->orderBy('category') ->execute() ->fetchAll(); // Loop through the categories and add them to the table. foreach ($categories as $category) { $rows[] = array( check_plain($category->category), check_plain($category->recipients), ($category->selected ? t('Yes') : t('No')), l(t('Edit'), 'admin/structure/contact/edit/' . $category->cid), l(t('Delete'), 'admin/structure/contact/delete/' . $category->cid), ); } if (!$rows) { $rows[] = array(array( 'data' => t('No categories available.'), 'colspan' => 5, )); } $build['category_table'] = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, ); return $build; } /** * Form constructor for the category edit form. * * @param $category * An array describing the category to be edited. May be empty for new * categories. Recognized array keys are: * - category: The name of the category. * - recipients: A comma-separated list of recipients. * - reply: (optional) The body of the auto-reply message. * - weight: The weight of the category. * - selected: Boolean indicating whether the category should be selected by * default. * - cid: The category ID for which the form is to be displayed. * * @see contact_category_edit_form_validate() * @see contact_category_edit_form_submit() * @ingroup forms */ function contact_category_edit_form($form, &$form_state, array $category = array()) { // If this is a new category, add the default values. $category += array( 'category' => '', 'recipients' => '', 'reply' => '', 'weight' => 0, 'selected' => 0, 'cid' => NULL, ); $form['category'] = array( '#type' => 'textfield', '#title' => t('Category'), '#maxlength' => 255, '#default_value' => $category['category'], '#description' => t("Example: 'website feedback' or 'product information'."), '#required' => TRUE, ); $form['recipients'] = array( '#type' => 'textarea', '#title' => t('Recipients'), '#default_value' => $category['recipients'], '#description' => t("Example: 'webmaster@example.com' or 'sales@example.com,support@example.com' . To specify multiple recipients, separate each e-mail address with a comma."), '#required' => TRUE, ); $form['reply'] = array( '#type' => 'textarea', '#title' => t('Auto-reply'), '#default_value' => $category['reply'], '#description' => t('Optional auto-reply. Leave empty if you do not want to send the user an auto-reply message.'), ); $form['weight'] = array( '#type' => 'weight', '#title' => t('Weight'), '#default_value' => $category['weight'], '#description' => t('When listing categories, those with lighter (smaller) weights get listed before categories with heavier (larger) weights. Categories with equal weights are sorted alphabetically.'), ); $form['selected'] = array( '#type' => 'select', '#title' => t('Selected'), '#options' => array( 0 => t('No'), 1 => t('Yes'), ), '#default_value' => $category['selected'], '#description' => t('Set this to Yes if you would like this category to be selected by default.'), ); $form['cid'] = array( '#type' => 'value', '#value' => $category['cid'], ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save'), ); return $form; } /** * Form validation handler for contact_category_edit_form(). * * @see contact_category_edit_form_submit() */ function contact_category_edit_form_validate($form, &$form_state) { // Validate and each e-mail recipient. $recipients = explode(',', $form_state['values']['recipients']); // When creating a new contact form, or renaming the category on an existing // contact form, make sure that the given category is unique. $category = $form_state['values']['category']; $query = db_select('contact', 'c')->condition('c.category', $category, '='); if (!empty($form_state['values']['cid'])) { $query->condition('c.cid', $form_state['values']['cid'], '<>'); } if ($query->countQuery()->execute()->fetchField()) { form_set_error('category', t('A contact form with category %category already exists.', array('%category' => $category))); } foreach ($recipients as &$recipient) { $recipient = trim($recipient); if (!valid_email_address($recipient)) { form_set_error('recipients', t('%recipient is an invalid e-mail address.', array('%recipient' => $recipient))); } } $form_state['values']['recipients'] = implode(',', $recipients); } /** * Form submission handler for contact_category_edit_form(). * * @see contact_category_edit_form_validate() */ function contact_category_edit_form_submit($form, &$form_state) { if ($form_state['values']['selected']) { // Unselect all other contact categories. db_update('contact') ->fields(array('selected' => '0')) ->execute(); } if (empty($form_state['values']['cid'])) { drupal_write_record('contact', $form_state['values']); } else { drupal_write_record('contact', $form_state['values'], array('cid')); } drupal_set_message(t('Category %category has been saved.', array('%category' => $form_state['values']['category']))); watchdog('contact', 'Category %category has been saved.', array('%category' => $form_state['values']['category']), WATCHDOG_NOTICE, l(t('Edit'), 'admin/structure/contact/edit/' . $form_state['values']['cid'])); $form_state['redirect'] = 'admin/structure/contact'; } /** * Form constructor for the contact category deletion form. * * @param $contact * Array describing the contact category to be deleted. See the documentation * of contact_category_edit_form() for the recognized keys. * * @see contact_menu() * @see contact_category_delete_form_submit() */ function contact_category_delete_form($form, &$form_state, array $contact) { $form['contact'] = array( '#type' => 'value', '#value' => $contact, ); return confirm_form( $form, t('Are you sure you want to delete %category?', array('%category' => $contact['category'])), 'admin/structure/contact', t('This action cannot be undone.'), t('Delete'), t('Cancel') ); } /** * Form submission handler for contact_category_delete_form(). */ function contact_category_delete_form_submit($form, &$form_state) { $contact = $form['contact']['#value']; db_delete('contact') ->condition('cid', $contact['cid']) ->execute(); drupal_set_message(t('Category %category has been deleted.', array('%category' => $contact['category']))); watchdog('contact', 'Category %category has been deleted.', array('%category' => $contact['category']), WATCHDOG_NOTICE); $form_state['redirect'] = 'admin/structure/contact'; } drupal-7.26/modules/contact/contact.module0000644001412200141220000002634012265562324020172 0ustar benderbender' . t('About') . ''; $output .= '

      ' . t('The Contact module allows visitors to contact site administrators and other users. Users specify a subject, write their message, and can have a copy of their message sent to their own e-mail address. For more information, see the online handbook entry for Contact module.', array('@contact' => 'http://drupal.org/documentation/modules/contact/')) . '

      '; $output .= '

      ' . t('Uses') . '

      '; $output .= '
      '; $output .= '
      ' . t('User contact forms') . '
      '; $output .= '
      ' . t('Site users can be contacted with a user contact form that keeps their e-mail address private. Users may enable or disable their personal contact forms by editing their My account page. If enabled, a Contact tab leads to a personal contact form displayed on their user profile. Site administrators are still able to use the contact form, even if has been disabled. The Contact tab is not shown when you view your own profile.') . '
      '; $output .= '
      ' . t('Site-wide contact forms') . '
      '; $output .= '
      ' . t('The Contact page provides a simple form for users with the Use the site-wide contact form permission to send comments, feedback, or other requests. You can create categories for directing the contact form messages to a set of defined recipients. Common categories for a business site, for example, might include "Website feedback" (messages are forwarded to website administrators) and "Product information" (messages are forwarded to members of the sales department). E-mail addresses defined within a category are not displayed publicly.', array('@contact' => url('contact'))) . '

      '; $output .= '
      ' . t('Navigation') . '
      '; $output .= '
      ' . t("When the site-wide contact form is enabled, a link in the main Navigation menu is created, but the link is disabled by default. This menu link can be enabled on the Menus administration page.", array('@contact' => url('contact'), '@menu' => url('admin/structure/menu'))) . '
      '; $output .= '
      ' . t('Customization') . '
      '; $output .= '
      ' . t('If you would like additional text to appear on the site-wide or personal contact page, use a block. You can create and edit blocks on the Blocks administration page.', array('@blocks' => url('admin/structure/block'))) . '
      '; $output .= '
      '; return $output; case 'admin/structure/contact': $output = '

      ' . t('Add one or more categories on this page to set up your site-wide contact form.', array('@form' => url('contact'))) . '

      '; $output .= '

      ' . t('A Contact menu item (disabled by default) is added to the Navigation menu, which you can modify on the Menus administration page.', array('@menu-settings' => url('admin/structure/menu'))) . '

      '; $output .= '

      ' . t('If you would like additional text to appear on the site-wide contact page, use a block. You can create and edit blocks on the Blocks administration page.', array('@blocks' => url('admin/structure/block'))) . '

      '; return $output; } } /** * Implements hook_permission(). */ function contact_permission() { return array( 'administer contact forms' => array( 'title' => t('Administer contact forms and contact form settings'), ), 'access site-wide contact form' => array( 'title' => t('Use the site-wide contact form'), ), 'access user contact forms' => array( 'title' => t("Use users' personal contact forms"), ), ); } /** * Implements hook_menu(). */ function contact_menu() { $items['admin/structure/contact'] = array( 'title' => 'Contact form', 'description' => 'Create a system contact form and set up categories for the form to use.', 'page callback' => 'contact_category_list', 'access arguments' => array('administer contact forms'), 'file' => 'contact.admin.inc', ); $items['admin/structure/contact/add'] = array( 'title' => 'Add category', 'page callback' => 'drupal_get_form', 'page arguments' => array('contact_category_edit_form'), 'access arguments' => array('administer contact forms'), 'type' => MENU_LOCAL_ACTION, 'weight' => 1, 'file' => 'contact.admin.inc', ); $items['admin/structure/contact/edit/%contact'] = array( 'title' => 'Edit contact category', 'page callback' => 'drupal_get_form', 'page arguments' => array('contact_category_edit_form', 4), 'access arguments' => array('administer contact forms'), 'file' => 'contact.admin.inc', ); $items['admin/structure/contact/delete/%contact'] = array( 'title' => 'Delete contact', 'page callback' => 'drupal_get_form', 'page arguments' => array('contact_category_delete_form', 4), 'access arguments' => array('administer contact forms'), 'file' => 'contact.admin.inc', ); $items['contact'] = array( 'title' => 'Contact', 'page callback' => 'drupal_get_form', 'page arguments' => array('contact_site_form'), 'access arguments' => array('access site-wide contact form'), 'type' => MENU_SUGGESTED_ITEM, 'file' => 'contact.pages.inc', ); $items['user/%user/contact'] = array( 'title' => 'Contact', 'page callback' => 'drupal_get_form', 'page arguments' => array('contact_personal_form', 1), 'type' => MENU_LOCAL_TASK, 'access callback' => '_contact_personal_tab_access', 'access arguments' => array(1), 'weight' => 2, 'file' => 'contact.pages.inc', ); return $items; } /** * Menu access callback for a user's personal contact form. * * @param $account * The user object of the user whose contact form is being requested. */ function _contact_personal_tab_access($account) { global $user; // Anonymous users cannot have contact forms. if (!$account->uid) { return FALSE; } // User administrators should always have access to personal contact forms. if (user_access('administer users')) { return TRUE; } // Users may not contact themselves. if ($user->uid == $account->uid) { return FALSE; } // If the requested user has disabled their contact form, or this preference // has not yet been saved, do not allow users to contact them. if (empty($account->data['contact'])) { return FALSE; } // If requested user has been blocked, do not allow users to contact them. if (empty($account->status)) { return FALSE; } return user_access('access user contact forms'); } /** * Loads a contact category. * * @param $cid * The contact category ID. * * @return * An array with the contact category's data. */ function contact_load($cid) { return db_select('contact', 'c') ->addTag('translatable') ->fields('c') ->condition('cid', $cid) ->execute() ->fetchAssoc(); } /** * Implements hook_mail(). */ function contact_mail($key, &$message, $params) { $language = $message['language']; $variables = array( '!site-name' => variable_get('site_name', 'Drupal'), '!subject' => $params['subject'], '!category' => isset($params['category']['category']) ? $params['category']['category'] : '', '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)), '!sender-name' => format_username($params['sender']), '!sender-url' => $params['sender']->uid ? url('user/' . $params['sender']->uid, array('absolute' => TRUE, 'language' => $language)) : $params['sender']->mail, ); switch ($key) { case 'page_mail': case 'page_copy': $message['subject'] .= t('[!category] !subject', $variables, array('langcode' => $language->language)); $message['body'][] = t("!sender-name (!sender-url) sent a message using the contact form at !form-url.", $variables, array('langcode' => $language->language)); $message['body'][] = $params['message']; break; case 'page_autoreply': $message['subject'] .= t('[!category] !subject', $variables, array('langcode' => $language->language)); $message['body'][] = $params['category']['reply']; break; case 'user_mail': case 'user_copy': $variables += array( '!recipient-name' => format_username($params['recipient']), '!recipient-edit-url' => url('user/' . $params['recipient']->uid . '/edit', array('absolute' => TRUE, 'language' => $language)), ); $message['subject'] .= t('[!site-name] !subject', $variables, array('langcode' => $language->language)); $message['body'][] = t('Hello !recipient-name,', $variables, array('langcode' => $language->language)); $message['body'][] = t("!sender-name (!sender-url) has sent you a message via your contact form (!form-url) at !site-name.", $variables, array('langcode' => $language->language)); $message['body'][] = t("If you don't want to receive such e-mails, you can change your settings at !recipient-edit-url.", $variables, array('langcode' => $language->language)); $message['body'][] = t('Message:', array(), array('langcode' => $language->language)); $message['body'][] = $params['message']; break; } } /** * Implements hook_form_FORM_ID_alter(). * * Add the enable personal contact form to an individual user's account page. * * @see user_profile_form() */ function contact_form_user_profile_form_alter(&$form, &$form_state) { if ($form['#user_category'] == 'account') { $account = $form['#user']; $form['contact'] = array( '#type' => 'fieldset', '#title' => t('Contact settings'), '#weight' => 5, '#collapsible' => TRUE, ); $form['contact']['contact'] = array( '#type' => 'checkbox', '#title' => t('Personal contact form'), '#default_value' => !empty($account->data['contact']) ? $account->data['contact'] : FALSE, '#description' => t('Allow other users to contact you via a personal contact form which keeps your e-mail address hidden. Note that some privileged users such as site administrators are still able to contact you even if you choose to disable this feature.', array('@url' => url("user/$account->uid/contact"))), ); } } /** * Implements hook_user_presave(). */ function contact_user_presave(&$edit, $account, $category) { $edit['data']['contact'] = isset($edit['contact']) ? $edit['contact'] : variable_get('contact_default_status', 1); } /** * Implements hook_form_FORM_ID_alter(). * * Add the default personal contact setting on the user settings page. * * @see user_admin_settings() */ function contact_form_user_admin_settings_alter(&$form, &$form_state) { $form['contact'] = array( '#type' => 'fieldset', '#title' => t('Contact settings'), '#weight' => 0, ); $form['contact']['contact_default_status'] = array( '#type' => 'checkbox', '#title' => t('Enable the personal contact form by default for new users.'), '#description' => t('Changing this setting will not affect existing users.'), '#default_value' => variable_get('contact_default_status', 1), ); } drupal-7.26/modules/contact/contact.test0000644001412200141220000004636712265562324017677 0ustar benderbender 'Site-wide contact form', 'description' => 'Tests site-wide contact form functionality.', 'group' => 'Contact', ); } function setUp() { parent::setUp('contact'); } /** * Tests configuration options and the site-wide contact form. */ function testSiteWideContact() { // Create and login administrative user. $admin_user = $this->drupalCreateUser(array('access site-wide contact form', 'administer contact forms', 'administer users')); $this->drupalLogin($admin_user); $flood_limit = 3; variable_set('contact_threshold_limit', $flood_limit); variable_set('contact_threshold_window', 600); // Set settings. $edit = array(); $edit['contact_default_status'] = TRUE; $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); $this->assertText(t('The configuration options have been saved.'), 'Setting successfully saved.'); // Delete old categories to ensure that new categories are used. $this->deleteCategories(); // Ensure that the contact form won't be shown without categories. user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access site-wide contact form')); $this->drupalLogout(); $this->drupalGet('contact'); $this->assertResponse(404); $this->drupalLogin($admin_user); $this->drupalGet('contact'); $this->assertResponse(200); $this->assertText(t('The contact form has not been configured.')); // Add categories. // Test invalid recipients. $invalid_recipients = array('invalid', 'invalid@', 'invalid@site.', '@site.', '@site.com'); foreach ($invalid_recipients as $invalid_recipient) { $this->addCategory($this->randomName(16), $invalid_recipient, '', FALSE); $this->assertRaw(t('%recipient is an invalid e-mail address.', array('%recipient' => $invalid_recipient)), format_string('Caught invalid recipient (@invalid_recipient).', array('@invalid_recipient' => $invalid_recipient))); } // Test validation of empty category and recipients fields. $this->addCategory($category = '', '', '', TRUE); $this->assertText(t('Category field is required.'), 'Caught empty category field'); $this->assertText(t('Recipients field is required.'), 'Caught empty recipients field.'); // Create first valid category. $recipients = array('simpletest@example.com', 'simpletest2@example.com', 'simpletest3@example.com'); $this->addCategory($category = $this->randomName(16), implode(',', array($recipients[0])), '', TRUE); $this->assertRaw(t('Category %category has been saved.', array('%category' => $category)), 'Category successfully saved.'); // Make sure the newly created category is included in the list of categories. $this->assertNoUniqueText($category, 'New category included in categories list.'); // Test update contact form category. $categories = $this->getCategories(); $category_id = $this->updateCategory($categories, $category = $this->randomName(16), $recipients_str = implode(',', array($recipients[0], $recipients[1])), $reply = $this->randomName(30), FALSE); $category_array = db_query("SELECT category, recipients, reply, selected FROM {contact} WHERE cid = :cid", array(':cid' => $category_id))->fetchAssoc(); $this->assertEqual($category_array['category'], $category); $this->assertEqual($category_array['recipients'], $recipients_str); $this->assertEqual($category_array['reply'], $reply); $this->assertFalse($category_array['selected']); $this->assertRaw(t('Category %category has been saved.', array('%category' => $category)), 'Category successfully saved.'); // Ensure that the contact form is shown without a category selection input. user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access site-wide contact form')); $this->drupalLogout(); $this->drupalGet('contact'); $this->assertText(t('Your e-mail address'), 'Contact form is shown when there is one category.'); $this->assertNoText(t('Category'), 'When there is only one category, the category selection element is hidden.'); $this->drupalLogin($admin_user); // Add more categories. $this->addCategory($category = $this->randomName(16), implode(',', array($recipients[0], $recipients[1])), '', FALSE); $this->assertRaw(t('Category %category has been saved.', array('%category' => $category)), 'Category successfully saved.'); $this->addCategory($category = $this->randomName(16), implode(',', array($recipients[0], $recipients[1], $recipients[2])), '', FALSE); $this->assertRaw(t('Category %category has been saved.', array('%category' => $category)), 'Category successfully saved.'); // Try adding a category that already exists. $this->addCategory($category, '', '', FALSE); $this->assertNoRaw(t('Category %category has been saved.', array('%category' => $category)), 'Category not saved.'); $this->assertRaw(t('A contact form with category %category already exists.', array('%category' => $category)), 'Duplicate category error found.'); // Clear flood table in preparation for flood test and allow other checks to complete. db_delete('flood')->execute(); $num_records_after = db_query("SELECT COUNT(*) FROM {flood}")->fetchField(); $this->assertIdentical($num_records_after, '0', 'Flood table emptied.'); $this->drupalLogout(); // Check to see that anonymous user cannot see contact page without permission. user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access site-wide contact form')); $this->drupalGet('contact'); $this->assertResponse(403, 'Access denied to anonymous user without permission.'); // Give anonymous user permission and see that page is viewable. user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access site-wide contact form')); $this->drupalGet('contact'); $this->assertResponse(200, 'Access granted to anonymous user with permission.'); // Submit contact form with invalid values. $this->submitContact('', $recipients[0], $this->randomName(16), $categories[0], $this->randomName(64)); $this->assertText(t('Your name field is required.'), 'Name required.'); $this->submitContact($this->randomName(16), '', $this->randomName(16), $categories[0], $this->randomName(64)); $this->assertText(t('Your e-mail address field is required.'), 'E-mail required.'); $this->submitContact($this->randomName(16), $invalid_recipients[0], $this->randomName(16), $categories[0], $this->randomName(64)); $this->assertText(t('You must enter a valid e-mail address.'), 'Valid e-mail required.'); $this->submitContact($this->randomName(16), $recipients[0], '', $categories[0], $this->randomName(64)); $this->assertText(t('Subject field is required.'), 'Subject required.'); $this->submitContact($this->randomName(16), $recipients[0], $this->randomName(16), $categories[0], ''); $this->assertText(t('Message field is required.'), 'Message required.'); // Test contact form with no default category selected. db_update('contact') ->fields(array('selected' => 0)) ->execute(); $this->drupalGet('contact'); $this->assertRaw(t('- Please choose -'), 'Without selected categories the visitor is asked to chose a category.'); // Submit contact form with invalid category id (cid 0). $this->submitContact($this->randomName(16), $recipients[0], $this->randomName(16), 0, ''); $this->assertText(t('You must select a valid category.'), 'Valid category required.'); // Submit contact form with correct values and check flood interval. for ($i = 0; $i < $flood_limit; $i++) { $this->submitContact($this->randomName(16), $recipients[0], $this->randomName(16), $categories[0], $this->randomName(64)); $this->assertText(t('Your message has been sent.'), 'Message sent.'); } // Submit contact form one over limit. $this->drupalGet('contact'); $this->assertResponse(403, 'Access denied to anonymous user after reaching message treshold.'); $this->assertRaw(t('You cannot send more than %number messages in @interval. Try again later.', array('%number' => variable_get('contact_threshold_limit', 3), '@interval' => format_interval(600))), 'Message threshold reached.'); // Delete created categories. $this->drupalLogin($admin_user); $this->deleteCategories(); } /** * Tests auto-reply on the site-wide contact form. */ function testAutoReply() { // Create and login administrative user. $admin_user = $this->drupalCreateUser(array('access site-wide contact form', 'administer contact forms', 'administer permissions', 'administer users')); $this->drupalLogin($admin_user); // Set up three categories, 2 with an auto-reply and one without. $foo_autoreply = $this->randomName(40); $bar_autoreply = $this->randomName(40); $this->addCategory('foo', 'foo@example.com', $foo_autoreply, FALSE); $this->addCategory('bar', 'bar@example.com', $bar_autoreply, FALSE); $this->addCategory('no_autoreply', 'bar@example.com', '', FALSE); // Test the auto-reply for category 'foo'. $email = $this->randomName(32) . '@example.com'; $subject = $this->randomName(64); $this->submitContact($this->randomName(16), $email, $subject, 2, $this->randomString(128)); // We are testing the auto-reply, so there should be one e-mail going to the sender. $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'foo@example.com')); $this->assertEqual(count($captured_emails), 1, 'Auto-reply e-mail was sent to the sender for category "foo".', 'Contact'); $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($foo_autoreply), 'Auto-reply e-mail body is correct for category "foo".', 'Contact'); // Test the auto-reply for category 'bar'. $email = $this->randomName(32) . '@example.com'; $this->submitContact($this->randomName(16), $email, $this->randomString(64), 3, $this->randomString(128)); // Auto-reply for category 'bar' should result in one auto-reply e-mail to the sender. $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'bar@example.com')); $this->assertEqual(count($captured_emails), 1, 'Auto-reply e-mail was sent to the sender for category "bar".', 'Contact'); $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($bar_autoreply), 'Auto-reply e-mail body is correct for category "bar".', 'Contact'); // Verify that no auto-reply is sent when the auto-reply field is left blank. $email = $this->randomName(32) . '@example.com'; $this->submitContact($this->randomName(16), $email, $this->randomString(64), 4, $this->randomString(128)); $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'no_autoreply@example.com')); $this->assertEqual(count($captured_emails), 0, 'No auto-reply e-mail was sent to the sender for category "no-autoreply".', 'Contact'); } /** * Adds a category. * * @param string $category * The category name. * @param string $recipients * The list of recipient e-mail addresses. * @param string $reply * The auto-reply text that is sent to a user upon completing the contact * form. * @param boolean $selected * Boolean indicating whether the category should be selected by default. */ function addCategory($category, $recipients, $reply, $selected) { $edit = array(); $edit['category'] = $category; $edit['recipients'] = $recipients; $edit['reply'] = $reply; $edit['selected'] = ($selected ? '1' : '0'); $this->drupalPost('admin/structure/contact/add', $edit, t('Save')); } /** * Updates a category. * * @param string $category * The category name. * @param string $recipients * The list of recipient e-mail addresses. * @param string $reply * The auto-reply text that is sent to a user upon completing the contact * form. * @param boolean $selected * Boolean indicating whether the category should be selected by default. */ function updateCategory($categories, $category, $recipients, $reply, $selected) { $category_id = $categories[array_rand($categories)]; $edit = array(); $edit['category'] = $category; $edit['recipients'] = $recipients; $edit['reply'] = $reply; $edit['selected'] = ($selected ? '1' : '0'); $this->drupalPost('admin/structure/contact/edit/' . $category_id, $edit, t('Save')); return ($category_id); } /** * Submits the contact form. * * @param string $name * The name of the sender. * @param string $mail * The e-mail address of the sender. * @param string $subject * The subject of the message. * @param integer $cid * The category ID of the message. * @param string $message * The message body. */ function submitContact($name, $mail, $subject, $cid, $message) { $edit = array(); $edit['name'] = $name; $edit['mail'] = $mail; $edit['subject'] = $subject; $edit['cid'] = $cid; $edit['message'] = $message; $this->drupalPost('contact', $edit, t('Send message')); } /** * Deletes all categories. */ function deleteCategories() { $categories = $this->getCategories(); foreach ($categories as $category) { $category_name = db_query("SELECT category FROM {contact} WHERE cid = :cid", array(':cid' => $category))->fetchField(); $this->drupalPost('admin/structure/contact/delete/' . $category, array(), t('Delete')); $this->assertRaw(t('Category %category has been deleted.', array('%category' => $category_name)), 'Category deleted successfully.'); } } /** * Gets a list of all category IDs. * * @return array * A list of the category IDs. */ function getCategories() { $categories = db_query('SELECT cid FROM {contact}')->fetchCol(); return $categories; } } /** * Tests the personal contact form. */ class ContactPersonalTestCase extends DrupalWebTestCase { private $admin_user; private $web_user; private $contact_user; public static function getInfo() { return array( 'name' => 'Personal contact form', 'description' => 'Tests personal contact form functionality.', 'group' => 'Contact', ); } function setUp() { parent::setUp('contact'); // Create an admin user. $this->admin_user = $this->drupalCreateUser(array('administer contact forms', 'administer users')); // Create some normal users with their contact forms enabled by default. variable_set('contact_default_status', TRUE); $this->web_user = $this->drupalCreateUser(array('access user contact forms')); $this->contact_user = $this->drupalCreateUser(); } /** * Tests access to the personal contact form. */ function testPersonalContactAccess() { // Test allowed access to user with contact form enabled. $this->drupalLogin($this->web_user); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(200); // Test denied access to the user's own contact form. $this->drupalGet('user/' . $this->web_user->uid . '/contact'); $this->assertResponse(403); // Test always denied access to the anonymous user contact form. $this->drupalGet('user/0/contact'); $this->assertResponse(403); // Test that anonymous users can access the contact form. $this->drupalLogout(); user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(200); // Revoke the personal contact permission for the anonymous user. user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(403); // Disable the personal contact form. $this->drupalLogin($this->admin_user); $edit = array('contact_default_status' => FALSE); $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); $this->assertText(t('The configuration options have been saved.'), 'Setting successfully saved.'); $this->drupalLogout(); // Re-create our contacted user with personal contact forms disabled by // default. $this->contact_user = $this->drupalCreateUser(); // Test denied access to a user with contact form disabled. $this->drupalLogin($this->web_user); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(403); // Test allowed access for admin user to a user with contact form disabled. $this->drupalLogin($this->admin_user); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(200); // Re-create our contacted user as a blocked user. $this->contact_user = $this->drupalCreateUser(); user_save($this->contact_user, array('status' => 0)); // Test that blocked users can still be contacted by admin. $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(200); // Test that blocked users cannot be contacted by non-admins. $this->drupalLogin($this->web_user); $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); $this->assertResponse(403); } /** * Tests the personal contact form flood protection. */ function testPersonalContactFlood() { $flood_limit = 3; variable_set('contact_threshold_limit', $flood_limit); // Clear flood table in preparation for flood test and allow other checks to complete. db_delete('flood')->execute(); $num_records_flood = db_query("SELECT COUNT(*) FROM {flood}")->fetchField(); $this->assertIdentical($num_records_flood, '0', 'Flood table emptied.'); $this->drupalLogin($this->web_user); // Submit contact form with correct values and check flood interval. for ($i = 0; $i < $flood_limit; $i++) { $this->submitPersonalContact($this->contact_user); $this->assertText(t('Your message has been sent.'), 'Message sent.'); } // Submit contact form one over limit. $this->drupalGet('user/' . $this->contact_user->uid. '/contact'); $this->assertRaw(t('You cannot send more than %number messages in @interval. Try again later.', array('%number' => $flood_limit, '@interval' => format_interval(variable_get('contact_threshold_window', 3600)))), 'Normal user denied access to flooded contact form.'); // Test that the admin user can still access the contact form even though // the flood limit was reached. $this->drupalLogin($this->admin_user); $this->assertNoText('Try again later.', 'Admin user not denied access to flooded contact form.'); } /** * Fills out a user's personal contact form and submits it. * * @param $account * A user object of the user being contacted. * @param $message * An optional array with the form fields being used. */ protected function submitPersonalContact($account, array $message = array()) { $message += array( 'subject' => $this->randomName(16), 'message' => $this->randomName(64), ); $this->drupalPost('user/' . $account->uid . '/contact', $message, t('Send message')); } } drupal-7.26/modules/contact/contact.info0000644001412200141220000000050212265564172017633 0ustar benderbendername = Contact description = Enables the use of both personal and site-wide contact forms. package = Core version = VERSION core = 7.x files[] = contact.test configure = admin/structure/contact ; Information added by Drupal.org packaging script on 2014-01-15 version = "7.26" project = "drupal" datestamp = "1389815930" drupal-7.26/modules/contact/contact.pages.inc0000644001412200141220000002313712265562324020555 0ustar benderbender $limit, '@interval' => format_interval($window))), 'error'); drupal_access_denied(); drupal_exit(); } // Get an array of the categories and the current default category. $categories = db_select('contact', 'c') ->addTag('translatable') ->fields('c', array('cid', 'category')) ->orderBy('weight') ->orderBy('category') ->execute() ->fetchAllKeyed(); $default_category = db_query("SELECT cid FROM {contact} WHERE selected = 1")->fetchField(); // If there are no categories, do not display the form. if (!$categories) { if (user_access('administer contact forms')) { drupal_set_message(t('The contact form has not been configured. Add one or more categories to the form.', array('@add' => url('admin/structure/contact/add'))), 'error'); } else { drupal_not_found(); drupal_exit(); } } // If there is more than one category available and no default category has // been selected, prepend a default placeholder value. if (!$default_category) { if (count($categories) > 1) { $categories = array(0 => t('- Please choose -')) + $categories; } else { $default_category = key($categories); } } if (!$user->uid) { $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; } $form['#attributes']['class'][] = 'contact-form'; $form['name'] = array( '#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 255, '#default_value' => $user->uid ? format_username($user) : '', '#required' => TRUE, ); $form['mail'] = array( '#type' => 'textfield', '#title' => t('Your e-mail address'), '#maxlength' => 255, '#default_value' => $user->uid ? $user->mail : '', '#required' => TRUE, ); $form['subject'] = array( '#type' => 'textfield', '#title' => t('Subject'), '#maxlength' => 255, '#required' => TRUE, ); $form['cid'] = array( '#type' => 'select', '#title' => t('Category'), '#default_value' => $default_category, '#options' => $categories, '#required' => TRUE, '#access' => count($categories) > 1, ); $form['message'] = array( '#type' => 'textarea', '#title' => t('Message'), '#required' => TRUE, ); // We do not allow anonymous users to send themselves a copy // because it can be abused to spam people. $form['copy'] = array( '#type' => 'checkbox', '#title' => t('Send yourself a copy.'), '#access' => $user->uid, ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Send message'), ); return $form; } /** * Form validation handler for contact_site_form(). * * @see contact_site_form_submit() */ function contact_site_form_validate($form, &$form_state) { if (!$form_state['values']['cid']) { form_set_error('cid', t('You must select a valid category.')); } if (!valid_email_address($form_state['values']['mail'])) { form_set_error('mail', t('You must enter a valid e-mail address.')); } } /** * Form submission handler for contact_site_form(). * * @see contact_site_form_validate() */ function contact_site_form_submit($form, &$form_state) { global $user, $language; $values = $form_state['values']; $values['sender'] = $user; $values['sender']->name = $values['name']; $values['sender']->mail = $values['mail']; $values['category'] = contact_load($values['cid']); // Save the anonymous user information to a cookie for reuse. if (!$user->uid) { user_cookie_save(array_intersect_key($values, array_flip(array('name', 'mail')))); } // Get the to and from e-mail addresses. $to = $values['category']['recipients']; $from = $values['sender']->mail; // Send the e-mail to the recipients using the site default language. drupal_mail('contact', 'page_mail', $to, language_default(), $values, $from); // If the user requests it, send a copy using the current language. if ($values['copy']) { drupal_mail('contact', 'page_copy', $from, $language, $values, $from); } // Send an auto-reply if necessary using the current language. if ($values['category']['reply']) { drupal_mail('contact', 'page_autoreply', $from, $language, $values, $to); } flood_register_event('contact', variable_get('contact_threshold_window', 3600)); watchdog('mail', '%sender-name (@sender-from) sent an e-mail regarding %category.', array('%sender-name' => $values['name'], '@sender-from' => $from, '%category' => $values['category']['category'])); // Jump to home page rather than back to contact page to avoid // contradictory messages if flood control has been activated. drupal_set_message(t('Your message has been sent.')); $form_state['redirect'] = ''; } /** * Form constructor for the personal contact form. * * Path: user/%user/contact * * @see contact_menu() * @see contact_personal_form_validate() * @see contact_personal_form_submit() * @ingroup forms */ function contact_personal_form($form, &$form_state, $recipient) { global $user; // Check if flood control has been activated for sending e-mails. $limit = variable_get('contact_threshold_limit', 5); $window = variable_get('contact_threshold_window', 3600); if (!flood_is_allowed('contact', $limit, $window) && !user_access('administer contact forms') && !user_access('administer users')) { drupal_set_message(t("You cannot send more than %limit messages in @interval. Try again later.", array('%limit' => $limit, '@interval' => format_interval($window))), 'error'); drupal_access_denied(); drupal_exit(); } drupal_set_title(t('Contact @username', array('@username' => format_username($recipient))), PASS_THROUGH); if (!$user->uid) { $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; } $form['#attributes']['class'][] = 'contact-form'; $form['recipient'] = array( '#type' => 'value', '#value' => $recipient, ); $form['name'] = array( '#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 255, '#default_value' => $user->uid ? format_username($user) : '', '#required' => TRUE, ); $form['mail'] = array( '#type' => 'textfield', '#title' => t('Your e-mail address'), '#maxlength' => 255, '#default_value' => $user->uid ? $user->mail : '', '#required' => TRUE, ); $form['to'] = array( '#type' => 'item', '#title' => t('To'), '#markup' => theme('username', array('account' => $recipient)), ); $form['subject'] = array( '#type' => 'textfield', '#title' => t('Subject'), '#maxlength' => 50, '#required' => TRUE, ); $form['message'] = array( '#type' => 'textarea', '#title' => t('Message'), '#rows' => 15, '#required' => TRUE, ); // We do not allow anonymous users to send themselves a copy // because it can be abused to spam people. $form['copy'] = array( '#type' => 'checkbox', '#title' => t('Send yourself a copy.'), '#access' => $user->uid, ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Send message'), ); return $form; } /** * Form validation handler for contact_personal_form(). * * @see contact_personal_form_submit() */ function contact_personal_form_validate($form, &$form_state) { if (!valid_email_address($form_state['values']['mail'])) { form_set_error('mail', t('You must enter a valid e-mail address.')); } } /** * Form submission handler for contact_personal_form(). * * @see contact_personal_form_validate() */ function contact_personal_form_submit($form, &$form_state) { global $user, $language; $values = $form_state['values']; $values['sender'] = $user; $values['sender']->name = $values['name']; $values['sender']->mail = $values['mail']; // Save the anonymous user information to a cookie for reuse. if (!$user->uid) { user_cookie_save(array_intersect_key($values, array_flip(array('name', 'mail')))); } // Get the to and from e-mail addresses. $to = $values['recipient']->mail; $from = $values['sender']->mail; // Send the e-mail in the requested user language. drupal_mail('contact', 'user_mail', $to, user_preferred_language($values['recipient']), $values, $from); // Send a copy if requested, using current page language. if ($values['copy']) { drupal_mail('contact', 'user_copy', $from, $language, $values, $from); } flood_register_event('contact', variable_get('contact_threshold_window', 3600)); watchdog('mail', '%sender-name (@sender-from) sent %recipient-name an e-mail.', array('%sender-name' => $values['name'], '@sender-from' => $from, '%recipient-name' => $values['recipient']->name)); // Jump to the contacted user's profile page. drupal_set_message(t('Your message has been sent.')); $form_state['redirect'] = user_access('access user profiles') ? 'user/' . $values['recipient']->uid : ''; } drupal-7.26/modules/translation/0000755001412200141220000000000012265562324016226 5ustar benderbenderdrupal-7.26/modules/translation/translation.pages.inc0000644001412200141220000000631612265562324022363 0ustar benderbendertnid) { // Already part of a set, grab that set. $tnid = $node->tnid; $translations = translation_node_get_translations($node->tnid); } else { // We have no translation source nid, this could be a new set, emulate that. $tnid = $node->nid; $translations = array(entity_language('node', $node) => $node); } $type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE); $header = array(t('Language'), t('Title'), t('Status'), t('Operations')); foreach (language_list() as $langcode => $language) { $options = array(); $language_name = $language->name; if (isset($translations[$langcode])) { // Existing translation in the translation set: display status. // We load the full node to check whether the user can edit it. $translation_node = node_load($translations[$langcode]->nid); $path = 'node/' . $translation_node->nid; $links = language_negotiation_get_switch_links($type, $path); $title = empty($links->links[$langcode]['href']) ? l($translation_node->title, $path) : l($translation_node->title, $links->links[$langcode]['href'], $links->links[$langcode]); if (node_access('update', $translation_node)) { $text = t('edit'); $path = 'node/' . $translation_node->nid . '/edit'; $links = language_negotiation_get_switch_links($type, $path); $options[] = empty($links->links[$langcode]['href']) ? l($text, $path) : l($text, $links->links[$langcode]['href'], $links->links[$langcode]); } $status = $translation_node->status ? t('Published') : t('Not published'); $status .= $translation_node->translate ? ' - ' . t('outdated') . '' : ''; if ($translation_node->nid == $tnid) { $language_name = t('@language_name (source)', array('@language_name' => $language_name)); } } else { // No such translation in the set yet: help user to create it. $title = t('n/a'); if (node_access('create', $node)) { $text = t('add translation'); $path = 'node/add/' . str_replace('_', '-', $node->type); $links = language_negotiation_get_switch_links($type, $path); $query = array('query' => array('translation' => $node->nid, 'target' => $langcode)); $options[] = empty($links->links[$langcode]['href']) ? l($text, $path, $query) : l($text, $links->links[$langcode]['href'], array_merge_recursive($links->links[$langcode], $query)); } $status = t('Not translated'); } $rows[] = array($language_name, $title, $status, implode(" | ", $options)); } drupal_set_title(t('Translations of %title', array('%title' => $node->title)), PASS_THROUGH); $build['translation_node_overview'] = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, ); return $build; } drupal-7.26/modules/translation/translation.info0000644001412200141220000000050212265564172021441 0ustar benderbendername = Content translation description = Allows content to be translated into different languages. dependencies[] = locale package = Core version = VERSION core = 7.x files[] = translation.test ; Information added by Drupal.org packaging script on 2014-01-15 version = "7.26" project = "drupal" datestamp = "1389815930" drupal-7.26/modules/translation/translation.test0000644001412200141220000005310712265562324021473 0ustar benderbender 'Translation functionality', 'description' => 'Create a basic page with translation, modify the page outdating translation, and update translation.', 'group' => 'Translation' ); } function setUp() { parent::setUp('locale', 'translation', 'translation_test'); // Setup users. $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate content')); $this->translator = $this->drupalCreateUser(array('create page content', 'edit own page content', 'translate content')); $this->drupalLogin($this->admin_user); // Add languages. $this->addLanguage('en'); $this->addLanguage('es'); $this->addLanguage('it'); // Disable Italian to test the translation behavior with disabled languages. $edit = array('enabled[it]' => FALSE); $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); // Set "Basic page" content type to use multilingual support with // translation. $this->drupalGet('admin/structure/types/manage/page'); $edit = array(); $edit['language_content_type'] = 2; $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.'); // Enable the language switcher block. $language_type = LANGUAGE_TYPE_INTERFACE; $edit = array("blocks[locale_$language_type][region]" => 'sidebar_first'); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Enable URL language detection and selection to make the language switcher // block appear. $edit = array('language[enabled][locale-url]' => TRUE); $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); $this->assertRaw(t('Language negotiation configuration saved.'), 'URL language detection enabled.'); $this->resetCaches(); $this->drupalLogin($this->translator); } /** * Creates, modifies, and updates a basic page with a translation. */ function testContentTranslation() { // Create Basic page in English. $node_title = $this->randomName(); $node_body = $this->randomName(); $node = $this->createPage($node_title, $node_body, 'en'); // Unpublish the original node to check that this has no impact on the // translation overview page, publish it again afterwards. $this->drupalLogin($this->admin_user); $this->drupalPost('node/' . $node->nid . '/edit', array('status' => FALSE), t('Save')); $this->drupalGet('node/' . $node->nid . '/translate'); $this->drupalPost('node/' . $node->nid . '/edit', array('status' => NODE_PUBLISHED), t('Save')); $this->drupalLogin($this->translator); // Check that the "add translation" link uses a localized path. $languages = language_list(); $this->drupalGet('node/' . $node->nid . '/translate'); $this->assertLinkByHref($languages['es']->prefix . '/node/add/' . str_replace('_', '-', $node->type), 0, format_string('The "add translation" link for %language points to the localized path of the target language.', array('%language' => $languages['es']->name))); // Submit translation in Spanish. $node_translation_title = $this->randomName(); $node_translation_body = $this->randomName(); $node_translation = $this->createTranslation($node, $node_translation_title, $node_translation_body, 'es'); // Check that the "edit translation" and "view node" links use localized // paths. $this->drupalGet('node/' . $node->nid . '/translate'); $this->assertLinkByHref($languages['es']->prefix . '/node/' . $node_translation->nid . '/edit', 0, format_string('The "edit" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name))); $this->assertLinkByHref($languages['es']->prefix . '/node/' . $node_translation->nid, 0, format_string('The "view" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name))); // Attempt to submit a duplicate translation by visiting the node/add page // with identical query string. $this->drupalGet('node/add/page', array('query' => array('translation' => $node->nid, 'target' => 'es'))); $this->assertRaw(t('A translation of %title in %language already exists', array('%title' => $node_title, '%language' => $languages['es']->name)), 'Message regarding attempted duplicate translation is displayed.'); // Attempt a resubmission of the form - this emulates using the back button // to return to the page then resubmitting the form without a refresh. $edit = array(); $langcode = LANGUAGE_NONE; $edit["title"] = $this->randomName(); $edit["body[$langcode][0][value]"] = $this->randomName(); $this->drupalPost('node/add/page', $edit, t('Save'), array('query' => array('translation' => $node->nid, 'language' => 'es'))); $duplicate = $this->drupalGetNodeByTitle($edit["title"]); $this->assertEqual($duplicate->tnid, 0, 'The node does not have a tnid.'); // Update original and mark translation as outdated. $node_body = $this->randomName(); $node->body[LANGUAGE_NONE][0]['value'] = $node_body; $edit = array(); $edit["body[$langcode][0][value]"] = $node_body; $edit['translation[retranslate]'] = TRUE; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node_title)), 'Original node updated.'); // Check to make sure that interface shows translation as outdated. $this->drupalGet('node/' . $node->nid . '/translate'); $this->assertRaw('' . t('outdated') . '', 'Translation marked as outdated.'); // Update translation and mark as updated. $edit = array(); $edit["body[$langcode][0][value]"] = $this->randomName(); $edit['translation[status]'] = FALSE; $this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node_translation_title)), 'Translated node updated.'); // Confirm that disabled languages are an option for translators when // creating nodes. $this->drupalGet('node/add/page'); $this->assertFieldByXPath('//select[@name="language"]//option', 'it', 'Italian (disabled) is available in language selection.'); $translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it'); $this->assertRaw($translation_it->body[LANGUAGE_NONE][0]['value'], 'Content created in Italian (disabled).'); // Confirm that language neutral is an option for translators when there are // disabled languages. $this->drupalGet('node/add/page'); $this->assertFieldByXPath('//select[@name="language"]//option', LANGUAGE_NONE, 'Language neutral is available in language selection with disabled languages.'); $node2 = $this->createPage($this->randomName(), $this->randomName(), LANGUAGE_NONE); $this->assertRaw($node2->body[LANGUAGE_NONE][0]['value'], 'Language neutral content created with disabled languages available.'); // Leave just one language enabled and check that the translation overview // page is still accessible. $this->drupalLogin($this->admin_user); $edit = array('enabled[es]' => FALSE); $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); $this->drupalLogin($this->translator); $this->drupalGet('node/' . $node->nid . '/translate'); $this->assertRaw(t('Translations of %title', array('%title' => $node->title)), 'Translation overview page available with only one language enabled.'); } /** * Checks that the language switch links behave properly. */ function testLanguageSwitchLinks() { // Create a Basic page in English and its translations in Spanish and // Italian. $node = $this->createPage($this->randomName(), $this->randomName(), 'en'); $translation_es = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'es'); $translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it'); // Check that language switch links are correctly shown only for enabled // languages. $this->assertLanguageSwitchLinks($node, $translation_es); $this->assertLanguageSwitchLinks($translation_es, $node); $this->assertLanguageSwitchLinks($node, $translation_it, FALSE); // Check that links to the displayed translation appear only in the language // switcher block. $this->assertLanguageSwitchLinks($node, $node, FALSE, 'node'); $this->assertLanguageSwitchLinks($node, $node, TRUE, 'block-locale'); // Unpublish the Spanish translation to check that the related language // switch link is not shown. $this->drupalLogin($this->admin_user); $edit = array('status' => FALSE); $this->drupalPost("node/$translation_es->nid/edit", $edit, t('Save')); $this->drupalLogin($this->translator); $this->assertLanguageSwitchLinks($node, $translation_es, FALSE); // Check that content translation links are shown even when no language // negotiation is configured. $this->drupalLogin($this->admin_user); $edit = array('language[enabled][locale-url]' => FALSE); $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); $this->resetCaches(); $edit = array('status' => TRUE); $this->drupalPost("node/$translation_es->nid/edit", $edit, t('Save')); $this->drupalLogin($this->translator); $this->assertLanguageSwitchLinks($node, $translation_es, TRUE, 'node'); } /** * Tests that the language switcher block alterations work as intended. */ function testLanguageSwitcherBlockIntegration() { // Enable Italian to have three items in the language switcher block. $this->drupalLogin($this->admin_user); $edit = array('enabled[it]' => TRUE); $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); $this->drupalLogin($this->translator); // Create a Basic page in English. $type = 'block-locale'; $node = $this->createPage($this->randomName(), $this->randomName(), 'en'); $this->assertLanguageSwitchLinks($node, $node, TRUE, $type); $this->assertLanguageSwitchLinks($node, $this->emptyNode('es'), TRUE, $type); $this->assertLanguageSwitchLinks($node, $this->emptyNode('it'), TRUE, $type); // Create the Spanish translation. $translation_es = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'es'); $this->assertLanguageSwitchLinks($node, $node, TRUE, $type); $this->assertLanguageSwitchLinks($node, $translation_es, TRUE, $type); $this->assertLanguageSwitchLinks($node, $this->emptyNode('it'), TRUE, $type); // Create the Italian translation. $translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it'); $this->assertLanguageSwitchLinks($node, $node, TRUE, $type); $this->assertLanguageSwitchLinks($node, $translation_es, TRUE, $type); $this->assertLanguageSwitchLinks($node, $translation_it, TRUE, $type); // Create a language neutral node and check that the language switcher is // left untouched. $node2 = $this->createPage($this->randomName(), $this->randomName(), LANGUAGE_NONE); $node2_en = (object) array('nid' => $node2->nid, 'language' => 'en'); $node2_es = (object) array('nid' => $node2->nid, 'language' => 'es'); $node2_it = (object) array('nid' => $node2->nid, 'language' => 'it'); $this->assertLanguageSwitchLinks($node2_en, $node2_en, TRUE, $type); $this->assertLanguageSwitchLinks($node2_en, $node2_es, TRUE, $type); $this->assertLanguageSwitchLinks($node2_en, $node2_it, TRUE, $type); // Disable translation support to check that the language switcher is left // untouched only for new nodes. $this->drupalLogin($this->admin_user); $edit = array('language_content_type' => 0); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); $this->drupalLogin($this->translator); // Existing translations trigger alterations even if translation support is // disabled. $this->assertLanguageSwitchLinks($node, $node, TRUE, $type); $this->assertLanguageSwitchLinks($node, $translation_es, TRUE, $type); $this->assertLanguageSwitchLinks($node, $translation_it, TRUE, $type); // Check that new nodes with a language assigned do not trigger language // switcher alterations when translation support is disabled. $node = $this->createPage($this->randomName(), $this->randomName()); $node_es = (object) array('nid' => $node->nid, 'language' => 'es'); $node_it = (object) array('nid' => $node->nid, 'language' => 'it'); $this->assertLanguageSwitchLinks($node, $node, TRUE, $type); $this->assertLanguageSwitchLinks($node, $node_es, TRUE, $type); $this->assertLanguageSwitchLinks($node, $node_it, TRUE, $type); } /** * Resets static caches to make the test code match the client-side behavior. */ function resetCaches() { drupal_static_reset('locale_url_outbound_alter'); } /** * Returns an empty node data structure. * * @param $langcode * The language code. * * @return * An empty node data structure. */ function emptyNode($langcode) { return (object) array('nid' => NULL, 'language' => $langcode); } /** * Installs the specified language, or enables it if it is already installed. * * @param $language_code * The language code to check. */ function addLanguage($language_code) { // Check to make sure that language has not already been installed. $this->drupalGet('admin/config/regional/language'); if (strpos($this->drupalGetContent(), 'enabled[' . $language_code . ']') === FALSE) { // Doesn't have language installed so add it. $edit = array(); $edit['langcode'] = $language_code; $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); // Make sure we are not using a stale list. drupal_static_reset('language_list'); $languages = language_list('language'); $this->assertTrue(array_key_exists($language_code, $languages), 'Language was installed successfully.'); if (array_key_exists($language_code, $languages)) { $this->assertRaw(t('The language %language has been created and can now be used. More information is available on the help screen.', array('%language' => $languages[$language_code]->name, '@locale-help' => url('admin/help/locale'))), 'Language has been created.'); } } elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $language_code . ']'))) { // It's installed and enabled. No need to do anything. $this->assertTrue(true, 'Language [' . $language_code . '] already installed and enabled.'); } else { // It's installed but not enabled. Enable it. $this->assertTrue(true, 'Language [' . $language_code . '] already installed.'); $this->drupalPost(NULL, array('enabled[' . $language_code . ']' => TRUE), t('Save configuration')); $this->assertRaw(t('Configuration saved.'), 'Language successfully enabled.'); } } /** * Creates a "Basic page" in the specified language. * * @param $title * The title of a basic page in the specified language. * @param $body * The body of a basic page in the specified language. * @param $language * (optional) Language code. * * @return * A node object. */ function createPage($title, $body, $language = NULL) { $edit = array(); $langcode = LANGUAGE_NONE; $edit["title"] = $title; $edit["body[$langcode][0][value]"] = $body; if (!empty($language)) { $edit['language'] = $language; } $this->drupalPost('node/add/page', $edit, t('Save')); $this->assertRaw(t('Basic page %title has been created.', array('%title' => $title)), 'Basic page created.'); // Check to make sure the node was created. $node = $this->drupalGetNodeByTitle($title); $this->assertTrue($node, 'Node found in database.'); return $node; } /** * Creates a translation for a basic page in the specified language. * * @param $node * The basic page to create the translation for. * @param $title * The title of a basic page in the specified language. * @param $body * The body of a basic page in the specified language. * @param $language * Language code. * * @return * Translation object. */ function createTranslation($node, $title, $body, $language) { $this->drupalGet('node/add/page', array('query' => array('translation' => $node->nid, 'target' => $language))); $langcode = LANGUAGE_NONE; $body_key = "body[$langcode][0][value]"; $this->assertFieldByXPath('//input[@id="edit-title"]', $node->title, "Original title value correctly populated."); $this->assertFieldByXPath("//textarea[@name='$body_key']", $node->body[LANGUAGE_NONE][0]['value'], "Original body value correctly populated."); $edit = array(); $edit["title"] = $title; $edit[$body_key] = $body; $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('Basic page %title has been created.', array('%title' => $title)), 'Translation created.'); // Check to make sure that translation was successful. $translation = $this->drupalGetNodeByTitle($title); $this->assertTrue($translation, 'Node found in database.'); $this->assertTrue($translation->tnid == $node->nid, 'Translation set id correctly stored.'); return $translation; } /** * Asserts an element identified by the given XPath has the given content. * * @param $xpath * The XPath used to find the element. * @param array $arguments * An array of arguments with keys in the form ':name' matching the * placeholders in the query. The values may be either strings or numeric * values. * @param $value * The text content of the matched element to assert. * @param $message * The message to display. * @param $group * The group this message belongs to. * * @return * TRUE on pass, FALSE on fail. */ function assertContentByXPath($xpath, array $arguments = array(), $value = NULL, $message = '', $group = 'Other') { $found = $this->findContentByXPath($xpath, $arguments, $value); return $this->assertTrue($found, $message, $group); } /** * Tests whether the specified language switch links are found. * * @param $node * The node to display. * @param $translation * The translation whose link has to be checked. * @param $find * TRUE if the link must be present in the node page. * @param $types * The page areas to be checked. * * @return * TRUE if the language switch links are found, FALSE if not. */ function assertLanguageSwitchLinks($node, $translation, $find = TRUE, $types = NULL) { if (empty($types)) { $types = array('node', 'block-locale'); } elseif (is_string($types)) { $types = array($types); } $result = TRUE; $languages = language_list(); $page_language = $languages[entity_language('node', $node)]; $translation_language = $languages[$translation->language]; $url = url("node/$translation->nid", array('language' => $translation_language)); $this->drupalGet("node/$node->nid", array('language' => $page_language)); foreach ($types as $type) { $args = array('%translation_language' => $translation_language->native, '%page_language' => $page_language->native, '%type' => $type); if ($find) { $message = format_string('[%page_language] Language switch item found for %translation_language language in the %type page area.', $args); } else { $message = format_string('[%page_language] Language switch item not found for %translation_language language in the %type page area.', $args); } if (!empty($translation->nid)) { $xpath = '//div[contains(@class, :type)]//a[@href=:url]'; } else { $xpath = '//div[contains(@class, :type)]//span[contains(@class, "locale-untranslated")]'; } $found = $this->findContentByXPath($xpath, array(':type' => $type, ':url' => $url), $translation_language->native); $result = $this->assertTrue($found == $find, $message) && $result; } return $result; } /** * Searches for elements matching the given xpath and value. * * @param $xpath * The XPath used to find the element. * @param array $arguments * An array of arguments with keys in the form ':name' matching the * placeholders in the query. The values may be either strings or numeric * values. * @param $value * The text content of the matched element to assert. * * @return * TRUE if found, otherwise FALSE. */ function findContentByXPath($xpath, array $arguments = array(), $value = NULL) { $elements = $this->xpath($xpath, $arguments); $found = TRUE; if ($value && $elements) { $found = FALSE; foreach ($elements as $element) { if ((string) $element == $value) { $found = TRUE; break; } } } return $elements && $found; } } drupal-7.26/modules/translation/translation.module0000644001412200141220000005417412265562324022006 0ustar benderbender' . t('About') . ''; $output .= '

      ' . t('The Content translation module allows content to be translated into different languages. Working with the Locale module (which manages enabled languages and provides translation for the site interface), the Content translation module is key to creating and maintaining translated site content. For more information, see the online handbook entry for Content translation module.', array('@locale' => url('admin/help/locale'), '@translation' => 'http://drupal.org/documentation/modules/translation/')) . '

      '; $output .= '

      ' . t('Uses') . '

      '; $output .= '
      '; $output .= '
      ' . t('Configuring content types for translation') . '
      '; $output .= '
      ' . t('To configure a particular content type for translation, visit the Content types page, and click the edit link for the content type. In the Publishing options section, select Enabled, with translation under Multilingual support.', array('@content-types' => url('admin/structure/types'))) . '
      '; $output .= '
      ' . t('Assigning a language to content') . '
      '; $output .= '
      ' . t('Use the Language drop down to select the appropriate language when creating or editing content.') . '
      '; $output .= '
      ' . t('Translating content') . '
      '; $output .= '
      ' . t('Users with the translate content permission can translate content, if the content type has been configured to allow translations. To translate content, select the Translation tab when viewing the content, select the language for which you wish to provide content, and then enter the content.') . '
      '; $output .= '
      ' . t('Maintaining translations') . '
      '; $output .= '
      ' . t('If editing content in one language requires that translated versions also be updated to reflect the change, use the Flag translations as outdated check box to mark the translations as outdated and in need of revision. Individual translations may also be marked for revision by selecting the This translation needs to be updated check box on the translation editing form.') . '
      '; $output .= '
      '; return $output; case 'node/%/translate': $output = '

      ' . t('Translations of a piece of content are managed with translation sets. Each translation set has one source post and any number of translations in any of the enabled languages. All translations are tracked to be up to date or outdated based on whether the source post was modified significantly.', array('!languages' => url('admin/config/regional/language'))) . '

      '; return $output; } } /** * Implements hook_menu(). */ function translation_menu() { $items = array(); $items['node/%node/translate'] = array( 'title' => 'Translate', 'page callback' => 'translation_node_overview', 'page arguments' => array(1), 'access callback' => '_translation_tab_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'weight' => 2, 'file' => 'translation.pages.inc', ); return $items; } /** * Access callback: Checks that the user has permission to 'translate content'. * * Only displays the translation tab for nodes that are not language-neutral * of types that have translation enabled. * * @param $node * A node object. * * @return * TRUE if the translation tab should be displayed, FALSE otherwise. * * @see translation_menu() */ function _translation_tab_access($node) { if (entity_language('node', $node) != LANGUAGE_NONE && translation_supported_type($node->type) && node_access('view', $node)) { return user_access('translate content'); } return FALSE; } /** * Implements hook_admin_paths(). */ function translation_admin_paths() { if (variable_get('node_admin_theme')) { $paths = array( 'node/*/translate' => TRUE, ); return $paths; } } /** * Implements hook_permission(). */ function translation_permission() { return array( 'translate content' => array( 'title' => t('Translate content'), ), ); } /** * Implements hook_form_FORM_ID_alter() for node_type_form(). */ function translation_form_node_type_form_alter(&$form, &$form_state) { // Add translation option to content type form. $form['workflow']['language_content_type']['#options'][TRANSLATION_ENABLED] = t('Enabled, with translation'); // Description based on text from locale.module. $form['workflow']['language_content_type']['#description'] = t('Enable multilingual support for this content type. If enabled, a language selection field will be added to the editing form, allowing you to select from one of the enabled languages. You can also turn on translation for this content type, which lets you have content translated to any of the installed languages. If disabled, new posts are saved with the default language. Existing content will not be affected by changing this option.', array('!languages' => url('admin/config/regional/language'))); } /** * Implements hook_form_BASE_FORM_ID_alter() for node_form(). * * Alters language fields on node edit forms when a translation is about to be * created. * * @see node_form() */ function translation_form_node_form_alter(&$form, &$form_state) { if (translation_supported_type($form['#node']->type)) { $node = $form['#node']; $languages = language_list('enabled'); $disabled_languages = isset($languages[0]) ? $languages[0] : FALSE; $translator_widget = $disabled_languages && user_access('translate content'); $groups = array(t('Disabled'), t('Enabled')); // Allow translators to enter content in disabled languages. Translators // might need to distinguish between enabled and disabled languages, hence // we divide them in two option groups. if ($translator_widget) { $options = array($groups[1] => array(LANGUAGE_NONE => t('Language neutral'))); $language_list = locale_language_list('name', TRUE); foreach (array(1, 0) as $status) { $group = $groups[$status]; foreach ($languages[$status] as $langcode => $language) { $options[$group][$langcode] = $language_list[$langcode]; } } $form['language']['#options'] = $options; } if (!empty($node->translation_source)) { // We are creating a translation. Add values and lock language field. $form['translation_source'] = array('#type' => 'value', '#value' => $node->translation_source); $form['language']['#disabled'] = TRUE; } elseif (!empty($node->nid) && !empty($node->tnid)) { // Disable languages for existing translations, so it is not possible to switch this // node to some language which is already in the translation set. Also remove the // language neutral option. unset($form['language']['#options'][LANGUAGE_NONE]); foreach (translation_node_get_translations($node->tnid) as $langcode => $translation) { if ($translation->nid != $node->nid) { if ($translator_widget) { $group = $groups[(int)!isset($disabled_languages[$langcode])]; unset($form['language']['#options'][$group][$langcode]); } else { unset($form['language']['#options'][$langcode]); } } } // Add translation values and workflow options. $form['tnid'] = array('#type' => 'value', '#value' => $node->tnid); $form['translation'] = array( '#type' => 'fieldset', '#title' => t('Translation settings'), '#access' => user_access('translate content'), '#collapsible' => TRUE, '#collapsed' => !$node->translate, '#tree' => TRUE, '#weight' => 30, ); if ($node->tnid == $node->nid) { // This is the source node of the translation $form['translation']['retranslate'] = array( '#type' => 'checkbox', '#title' => t('Flag translations as outdated'), '#default_value' => 0, '#description' => t('If you made a significant change, which means translations should be updated, you can flag all translations of this post as outdated. This will not change any other property of those posts, like whether they are published or not.'), ); $form['translation']['status'] = array('#type' => 'value', '#value' => 0); } else { $form['translation']['status'] = array( '#type' => 'checkbox', '#title' => t('This translation needs to be updated'), '#default_value' => $node->translate, '#description' => t('When this option is checked, this translation needs to be updated because the source post has changed. Uncheck when the translation is up to date again.'), ); } } } } /** * Implements hook_node_view(). * * Displays translation links with language names if this node is part of a * translation set. If no language provider is enabled, "fall back" to simple * links built through the result of translation_node_get_translations(). */ function translation_node_view($node, $view_mode) { // If the site has no translations or is not multilingual we have no content // translation links to display. if (isset($node->tnid) && drupal_multilingual() && $translations = translation_node_get_translations($node->tnid)) { $languages = language_list('enabled'); $languages = $languages[1]; // There might be a language provider enabled defining custom language // switch links which need to be taken into account while generating the // content translation links. As custom language switch links are available // only for configurable language types and interface language is the only // configurable language type in core, we use it as default. Contributed // modules can change this behavior by setting the system variable below. $type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE); $custom_links = language_negotiation_get_switch_links($type, "node/$node->nid"); $links = array(); foreach ($translations as $langcode => $translation) { // Do not show links to the same node, to unpublished translations or to // translations in disabled languages. if ($translation->status && isset($languages[$langcode]) && $langcode != entity_language('node', $node)) { $language = $languages[$langcode]; $key = "translation_$langcode"; if (isset($custom_links->links[$langcode])) { $links[$key] = $custom_links->links[$langcode]; } else { $links[$key] = array( 'href' => "node/{$translation->nid}", 'title' => $language->native, 'language' => $language, ); } // Custom switch links are more generic than content translation links, // hence we override existing attributes with the ones below. $links[$key] += array('attributes' => array()); $attributes = array( 'title' => $translation->title, 'class' => array('translation-link'), ); $links[$key]['attributes'] = $attributes + $links[$key]['attributes']; } } $node->content['links']['translation'] = array( '#theme' => 'links__node__translation', '#links' => $links, '#attributes' => array('class' => array('links', 'inline')), ); } } /** * Implements hook_node_prepare(). */ function translation_node_prepare($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type) && // And it's a new node. empty($node->nid) && // And the user has permission to translate content. user_access('translate content') && // And the $_GET variables are set properly. isset($_GET['translation']) && isset($_GET['target']) && is_numeric($_GET['translation'])) { $source_node = node_load($_GET['translation']); if (empty($source_node) || !node_access('view', $source_node)) { // Source node not found or no access to view. We should not check // for edit access, since the translator might not have permissions // to edit the source node but should still be able to translate. return; } $language_list = language_list(); $langcode = $_GET['target']; if (!isset($language_list[$langcode]) || ($source_node->language == $langcode)) { // If not supported language, or same language as source node, break. return; } // Ensure we don't have an existing translation in this language. if (!empty($source_node->tnid)) { $translations = translation_node_get_translations($source_node->tnid); if (isset($translations[$langcode])) { drupal_set_message(t('A translation of %title in %language already exists, a new %type will be created instead of a translation.', array('%title' => $source_node->title, '%language' => $language_list[$langcode]->name, '%type' => $node->type)), 'error'); return; } } // Populate fields based on source node. $node->language = $langcode; $node->translation_source = $source_node; $node->title = $source_node->title; // Add field translations and let other modules module add custom translated // fields. field_attach_prepare_translation('node', $node, $langcode, $source_node, $source_node->language); } } /** * Implements hook_node_insert(). */ function translation_node_insert($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type)) { if (!empty($node->translation_source)) { if ($node->translation_source->tnid) { // Add node to existing translation set. $tnid = $node->translation_source->tnid; } else { // Create new translation set, using nid from the source node. $tnid = $node->translation_source->nid; db_update('node') ->fields(array( 'tnid' => $tnid, 'translate' => 0, )) ->condition('nid', $node->translation_source->nid) ->execute(); } db_update('node') ->fields(array( 'tnid' => $tnid, 'translate' => 0, )) ->condition('nid', $node->nid) ->execute(); // Save tnid to avoid loss in case of resave. $node->tnid = $tnid; } } } /** * Implements hook_node_update(). */ function translation_node_update($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type)) { $langcode = entity_language('node', $node); if (isset($node->translation) && $node->translation && !empty($langcode) && $node->tnid) { // Update translation information. db_update('node') ->fields(array( 'tnid' => $node->tnid, 'translate' => $node->translation['status'], )) ->condition('nid', $node->nid) ->execute(); if (!empty($node->translation['retranslate'])) { // This is the source node, asking to mark all translations outdated. db_update('node') ->fields(array('translate' => 1)) ->condition('nid', $node->nid, '<>') ->condition('tnid', $node->tnid) ->execute(); } } } } /** * Implements hook_node_validate(). * * Ensures that duplicate translations can't be created for the same source. */ function translation_node_validate($node, $form) { // Only act on translatable nodes with a tnid or translation_source. if (translation_supported_type($node->type) && (!empty($node->tnid) || !empty($form['#node']->translation_source->nid))) { $tnid = !empty($node->tnid) ? $node->tnid : $form['#node']->translation_source->nid; $translations = translation_node_get_translations($tnid); $langcode = entity_language('node', $node); if (isset($translations[$langcode]) && $translations[$langcode]->nid != $node->nid ) { form_set_error('language', t('There is already a translation in this language.')); } } } /** * Implements hook_node_delete(). */ function translation_node_delete($node) { // Only act if we are dealing with a content type supporting translations. if (translation_supported_type($node->type)) { translation_remove_from_set($node); } } /** * Removes a node from its translation set and updates accordingly. * * @param $node * A node object. */ function translation_remove_from_set($node) { if (isset($node->tnid)) { $query = db_update('node') ->fields(array( 'tnid' => 0, 'translate' => 0, )); if (db_query('SELECT COUNT(*) FROM {node} WHERE tnid = :tnid', array(':tnid' => $node->tnid))->fetchField() == 1) { // There is only one node left in the set: remove the set altogether. $query ->condition('tnid', $node->tnid) ->execute(); } else { $query ->condition('nid', $node->nid) ->execute(); // If the node being removed was the source of the translation set, // we pick a new source - preferably one that is up to date. if ($node->tnid == $node->nid) { $new_tnid = db_query('SELECT nid FROM {node} WHERE tnid = :tnid ORDER BY translate ASC, nid ASC', array(':tnid' => $node->tnid))->fetchField(); db_update('node') ->fields(array('tnid' => $new_tnid)) ->condition('tnid', $node->tnid) ->execute(); } } } } /** * Gets all nodes in a given translation set. * * @param $tnid * The translation source nid of the translation set, the identifier of the * node used to derive all translations in the set. * * @return * Array of partial node objects (nid, title, language) representing all * nodes in the translation set, in effect all translations of node $tnid, * including node $tnid itself. Because these are partial nodes, you need to * node_load() the full node, if you need more properties. The array is * indexed by language code. */ function translation_node_get_translations($tnid) { if (is_numeric($tnid) && $tnid) { $translations = &drupal_static(__FUNCTION__, array()); if (!isset($translations[$tnid])) { $translations[$tnid] = array(); $result = db_select('node', 'n') ->fields('n', array('nid', 'type', 'uid', 'status', 'title', 'language')) ->condition('n.tnid', $tnid) ->addTag('node_access') ->execute(); foreach ($result as $node) { $langcode = entity_language('node', $node); $translations[$tnid][$langcode] = $node; } } return $translations[$tnid]; } } /** * Returns whether the given content type has support for translations. * * @return * TRUE if translation is supported, and FALSE if not. */ function translation_supported_type($type) { return variable_get('language_content_type_' . $type, 0) == TRANSLATION_ENABLED; } /** * Returns the paths of all translations of a node, based on its Drupal path. * * @param $path * A Drupal path, for example node/432. * * @return * An array of paths of translations of the node accessible to the current * user, keyed with language codes. */ function translation_path_get_translations($path) { $paths = array(); // Check for a node related path, and for its translations. if ((preg_match("!^node/(\d+)(/.+|)$!", $path, $matches)) && ($node = node_load((int) $matches[1])) && !empty($node->tnid)) { foreach (translation_node_get_translations($node->tnid) as $language => $translation_node) { $paths[$language] = 'node/' . $translation_node->nid . $matches[2]; } } return $paths; } /** * Implements hook_language_switch_links_alter(). * * Replaces links with pointers to translated versions of the content. */ function translation_language_switch_links_alter(array &$links, $type, $path) { $language_type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE); if ($type == $language_type && preg_match("!^node/(\d+)(/.+|)!", $path, $matches)) { $node = node_load((int) $matches[1]); if (empty($node->tnid)) { // If the node cannot be found nothing needs to be done. If it does not // have translations it might be a language neutral node, in which case we // must leave the language switch links unaltered. This is true also for // nodes not having translation support enabled. if (empty($node) || entity_language('node', $node) == LANGUAGE_NONE || !translation_supported_type($node->type)) { return; } $langcode = entity_language('node', $node); $translations = array($langcode => $node); } else { $translations = translation_node_get_translations($node->tnid); } foreach ($links as $langcode => $link) { if (isset($translations[$langcode]) && $translations[$langcode]->status) { // Translation in a different node. $links[$langcode]['href'] = 'node/' . $translations[$langcode]->nid . $matches[2]; } else { // No translation in this language, or no permission to view. unset($links[$langcode]['href']); $links[$langcode]['attributes']['class'][] = 'locale-untranslated'; } } } } drupal-7.26/modules/translation/tests/0000755001412200141220000000000012265562324017370 5ustar benderbenderdrupal-7.26/modules/translation/tests/translation_test.module0000644001412200141220000000031712265562324024175 0ustar benderbender 'PHP code'))->fetchField(); // Add a PHP code text format, if it does not exist. Do this only for the // first install (or if the format has been manually deleted) as there is no // reliable method to identify the format in an uninstall hook or in // subsequent clean installs. if (!$format_exists) { $php_format = array( 'format' => 'php_code', 'name' => 'PHP code', // 'Plain text' format is installed with a weight of 10 by default. Use a // higher weight here to ensure that this format will not be the default // format for anyone. 'weight' => 11, 'filters' => array( // Enable the PHP evaluator filter. 'php_code' => array( 'weight' => 0, 'status' => 1, ), ), ); $php_format = (object) $php_format; filter_format_save($php_format); drupal_set_message(t('A PHP code text format has been created.', array('@php-code' => url('admin/config/content/formats/' . $php_format->format)))); } } /** * Implements hook_disable(). */ function php_disable() { drupal_set_message(t('The PHP module has been disabled. Any existing content that was using the PHP filter will now be visible in plain text. This might pose a security risk by exposing sensitive information, if any, used in the PHP code.')); } drupal-7.26/modules/php/php.test0000644001412200141220000001072712265562324016156 0ustar benderbenderdrupalCreateUser(array('administer filters')); $this->drupalLogin($admin_user); // Verify that the PHP code text format was inserted. $php_format_id = 'php_code'; $this->php_code_format = filter_format_load($php_format_id); $this->assertEqual($this->php_code_format->name, 'PHP code', 'PHP code text format was created.'); // Verify that the format has the PHP code filter enabled. $filters = filter_list_format($php_format_id); $this->assertTrue($filters['php_code']->status, 'PHP code filter is enabled.'); // Verify that the format exists on the administration page. $this->drupalGet('admin/config/content/formats'); $this->assertText('PHP code', 'PHP code text format was created.'); // Verify that anonymous and authenticated user roles do not have access. $this->drupalGet('admin/config/content/formats/' . $php_format_id); $this->assertFieldByName('roles[' . DRUPAL_ANONYMOUS_RID . ']', FALSE, 'Anonymous users do not have access to PHP code format.'); $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', FALSE, 'Authenticated users do not have access to PHP code format.'); } /** * Creates a test node with PHP code in the body. * * @return stdObject Node object. */ function createNodeWithCode() { return $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => ''))))); } } /** * Tests to make sure the PHP filter actually evaluates PHP code when used. */ class PHPFilterTestCase extends PHPTestCase { public static function getInfo() { return array( 'name' => 'PHP filter functionality', 'description' => 'Make sure that PHP filter properly evaluates PHP code when enabled.', 'group' => 'PHP', ); } /** * Makes sure that the PHP filter evaluates PHP code when used. */ function testPHPFilter() { // Log in as a user with permission to use the PHP code text format. $php_code_permission = filter_permission_name(filter_format_load('php_code')); $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content', $php_code_permission)); $this->drupalLogin($web_user); // Create a node with PHP code in it. $node = $this->createNodeWithCode(); // Make sure that the PHP code shows up as text. $this->drupalGet('node/' . $node->nid); $this->assertText('print "SimpleTest PHP was executed!"', 'PHP code is displayed.'); // Change filter to PHP filter and see that PHP code is evaluated. $edit = array(); $langcode = LANGUAGE_NONE; $edit["body[$langcode][0][format]"] = $this->php_code_format->format; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node->title)), 'PHP code filter turned on.'); // Make sure that the PHP code shows up as text. $this->assertNoText('print "SimpleTest PHP was executed!"', "PHP code isn't displayed."); $this->assertText('SimpleTest PHP was executed!', 'PHP code has been evaluated.'); } } /** * Tests to make sure access to the PHP filter is properly restricted. */ class PHPAccessTestCase extends PHPTestCase { public static function getInfo() { return array( 'name' => 'PHP filter access check', 'description' => 'Make sure that users who don\'t have access to the PHP filter can\'t see it.', 'group' => 'PHP', ); } /** * Makes sure that the user can't use the PHP filter when not given access. */ function testNoPrivileges() { // Create node with PHP filter enabled. $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content')); $this->drupalLogin($web_user); $node = $this->createNodeWithCode(); // Make sure that the PHP code shows up as text. $this->drupalGet('node/' . $node->nid); $this->assertText('print', 'PHP code was not evaluated.'); // Make sure that user doesn't have access to filter. $this->drupalGet('node/' . $node->nid . '/edit'); $this->assertNoRaw(''; }); $(this).html(html).attr('disabled', disabled ? 'disabled' : false); }); }; Drupal.behaviors.fieldUIDisplayOverview = { attach: function (context, settings) { $('table#field-display-overview', context).once('field-display-overview', function() { Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview); }); } }; Drupal.fieldUIOverview = { /** * Attaches the fieldUIOverview behavior. */ attach: function (table, rowsData, rowHandlers) { var tableDrag = Drupal.tableDrag[table.id]; // Add custom tabledrag callbacks. tableDrag.onDrop = this.onDrop; tableDrag.row.prototype.onSwap = this.onSwap; // Create row handlers. $('tr.draggable', table).each(function () { // Extract server-side data for the row. var row = this; if (row.id in rowsData) { var data = rowsData[row.id]; data.tableDrag = tableDrag; // Create the row handler, make it accessible from the DOM row element. var rowHandler = new rowHandlers[data.rowHandler](row, data); $(row).data('fieldUIRowHandler', rowHandler); } }); }, /** * Event handler to be attached to form inputs triggering a region change. */ onChange: function () { var $trigger = $(this); var row = $trigger.closest('tr').get(0); var rowHandler = $(row).data('fieldUIRowHandler'); var refreshRows = {}; refreshRows[rowHandler.name] = $trigger.get(0); // Handle region change. var region = rowHandler.getRegion(); if (region != rowHandler.region) { // Remove parenting. $('select.field-parent', row).val(''); // Let the row handler deal with the region change. $.extend(refreshRows, rowHandler.regionChange(region)); // Update the row region. rowHandler.region = region; } // Ajax-update the rows. Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows); }, /** * Lets row handlers react when a row is dropped into a new region. */ onDrop: function () { var dragObject = this; var row = dragObject.rowObject.element; var rowHandler = $(row).data('fieldUIRowHandler'); if (rowHandler !== undefined) { var regionRow = $(row).prevAll('tr.region-message').get(0); var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2'); if (region != rowHandler.region) { // Let the row handler deal with the region change. refreshRows = rowHandler.regionChange(region); // Update the row region. rowHandler.region = region; // Ajax-update the rows. Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows); } } }, /** * Refreshes placeholder rows in empty regions while a row is being dragged. * * Copied from block.js. * * @param table * The table DOM element. * @param rowObject * The tableDrag rowObject for the row being dragged. */ onSwap: function (draggedRow) { var rowObject = this; $('tr.region-message', rowObject.table).each(function () { // If the dragged row is in this region, but above the message row, swap // it down one space. if ($(this).prev('tr').get(0) == rowObject.group[rowObject.group.length - 1]) { // Prevent a recursion problem when using the keyboard to move rows up. if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) { rowObject.swap('after', this); } } // This region has become empty. if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').length == 0) { $(this).removeClass('region-populated').addClass('region-empty'); } // This region has become populated. else if ($(this).is('.region-empty')) { $(this).removeClass('region-empty').addClass('region-populated'); } }); }, /** * Triggers Ajax refresh of selected rows. * * The 'format type' selects can trigger a series of changes in child rows. * The #ajax behavior is therefore not attached directly to the selects, but * triggered manually through a hidden #ajax 'Refresh' button. * * @param rows * A hash object, whose keys are the names of the rows to refresh (they * will receive the 'ajax-new-content' effect on the server side), and * whose values are the DOM element in the row that should get an Ajax * throbber. */ AJAXRefreshRows: function (rows) { // Separate keys and values. var rowNames = []; var ajaxElements = []; $.each(rows, function (rowName, ajaxElement) { rowNames.push(rowName); ajaxElements.push(ajaxElement); }); if (rowNames.length) { // Add a throbber next each of the ajaxElements. var $throbber = $('
       
      '); $(ajaxElements) .addClass('progress-disabled') .after($throbber); // Fire the Ajax update. $('input[name=refresh_rows]').val(rowNames.join(' ')); $('input#edit-refresh').mousedown(); // Disabled elements do not appear in POST ajax data, so we mark the // elements disabled only after firing the request. $(ajaxElements).attr('disabled', true); } } }; /** * Row handlers for the 'Manage display' screen. */ Drupal.fieldUIDisplayOverview = {}; /** * Constructor for a 'field' row handler. * * This handler is used for both fields and 'extra fields' rows. * * @param row * The row DOM element. * @param data * Additional data to be populated in the constructed object. */ Drupal.fieldUIDisplayOverview.field = function (row, data) { this.row = row; this.name = data.name; this.region = data.region; this.tableDrag = data.tableDrag; // Attach change listener to the 'formatter type' select. this.$formatSelect = $('select.field-formatter-type', row); this.$formatSelect.change(Drupal.fieldUIOverview.onChange); return this; }; Drupal.fieldUIDisplayOverview.field.prototype = { /** * Returns the region corresponding to the current form values of the row. */ getRegion: function () { return (this.$formatSelect.val() == 'hidden') ? 'hidden' : 'visible'; }, /** * Reacts to a row being changed regions. * * This function is called when the row is moved to a different region, as a * result of either : * - a drag-and-drop action (the row's form elements then probably need to be * updated accordingly) * - user input in one of the form elements watched by the * Drupal.fieldUIOverview.onChange change listener. * * @param region * The name of the new region for the row. * @return * A hash object indicating which rows should be Ajax-updated as a result * of the change, in the format expected by * Drupal.displayOverview.AJAXRefreshRows(). */ regionChange: function (region) { // When triggered by a row drag, the 'format' select needs to be adjusted // to the new region. var currentValue = this.$formatSelect.val(); switch (region) { case 'visible': if (currentValue == 'hidden') { // Restore the formatter back to the default formatter. Pseudo-fields do // not have default formatters, we just return to 'visible' for those. var value = (this.defaultFormatter != undefined) ? this.defaultFormatter : 'visible'; } break; default: var value = 'hidden'; break; } if (value != undefined) { this.$formatSelect.val(value); } var refreshRows = {}; refreshRows[this.name] = this.$formatSelect.get(0); return refreshRows; } }; })(jQuery); drupal-7.26/modules/field_ui/field_ui.admin.inc0000644001412200141220000023266112265562324021024 0ustar benderbender $type_bundles) { foreach ($type_bundles as $bundle => $bundle_instances) { foreach ($bundle_instances as $field_name => $instance) { $field = field_info_field($field_name); // Initialize the row if we encounter the field for the first time. if (!isset($rows[$field_name])) { $rows[$field_name]['class'] = $field['locked'] ? array('menu-disabled') : array(''); $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; $module_name = $field_types[$field['type']]['module']; $rows[$field_name]['data'][1] = $field_types[$field['type']]['label'] . ' ' . t('(module: !module)', array('!module' => $modules[$module_name]->info['name'])); } // Add the current instance. $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); $rows[$field_name]['data'][2][] = $admin_path ? l($bundles[$entity_type][$bundle]['label'], $admin_path . '/fields') : $bundles[$entity_type][$bundle]['label']; } } } foreach ($rows as $field_name => $cell) { $rows[$field_name]['data'][2] = implode(', ', $cell['data'][2]); } if (empty($rows)) { $output = t('No fields have been defined yet.'); } else { // Sort rows by field name. ksort($rows); $output = theme('table', array('header' => $header, 'rows' => $rows)); } return $output; } /** * Displays a message listing the inactive fields of a given bundle. */ function field_ui_inactive_message($entity_type, $bundle) { $inactive_instances = field_ui_inactive_instances($entity_type, $bundle); if (!empty($inactive_instances)) { $field_types = field_info_field_types(); $widget_types = field_info_widget_types(); foreach ($inactive_instances as $field_name => $instance) { $list[] = t('%field (@field_name) field requires the %widget_type widget provided by %widget_module module', array( '%field' => $instance['label'], '@field_name' => $instance['field_name'], '%widget_type' => isset($widget_types[$instance['widget']['type']]) ? $widget_types[$instance['widget']['type']]['label'] : $instance['widget']['type'], '%widget_module' => $instance['widget']['module'], )); } drupal_set_message(t('Inactive fields are not shown unless their providing modules are enabled. The following fields are not enabled: !list', array('!list' => theme('item_list', array('items' => $list)))), 'error'); } } /** * Determines the rendering order of an array representing a tree. * * Callback for array_reduce() within field_ui_table_pre_render(). */ function _field_ui_reduce_order($array, $a) { $array = !isset($array) ? array() : $array; if ($a['name']) { $array[] = $a['name']; } if (!empty($a['children'])) { uasort($a['children'], 'drupal_sort_weight'); $array = array_merge($array, array_reduce($a['children'], '_field_ui_reduce_order')); } return $array; } /** * Returns the region to which a row in the 'Manage fields' screen belongs. * * This function is used as a #region_callback in * field_ui_field_overview_form(). It is called during * field_ui_table_pre_render(). */ function field_ui_field_overview_row_region($row) { switch ($row['#row_type']) { case 'field': case 'extra_field': return 'main'; case 'add_new_field': // If no input in 'label', assume the row has not been dragged out of the // 'add new' section. return (!empty($row['label']['#value']) ? 'main' : 'add_new'); } } /** * Returns the region to which a row in the 'Manage display' screen belongs. * * This function is used as a #region_callback in * field_ui_field_overview_form(), and is called during * field_ui_table_pre_render(). */ function field_ui_display_overview_row_region($row) { switch ($row['#row_type']) { case 'field': case 'extra_field': return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'visible'); } } /** * Pre-render callback for field_ui_table elements. */ function field_ui_table_pre_render($elements) { $js_settings = array(); // For each region, build the tree structure from the weight and parenting // data contained in the flat form structure, to determine row order and // indentation. $regions = $elements['#regions']; $tree = array('' => array('name' => '', 'children' => array())); $trees = array_fill_keys(array_keys($regions), $tree); $parents = array(); $list = drupal_map_assoc(element_children($elements)); // Iterate on rows until we can build a known tree path for all of them. while ($list) { foreach ($list as $name) { $row = &$elements[$name]; $parent = $row['parent_wrapper']['parent']['#value']; // Proceed if parent is known. if (empty($parent) || isset($parents[$parent])) { // Grab parent, and remove the row from the next iteration. $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array(); unset($list[$name]); // Determine the region for the row. $function = $row['#region_callback']; $region_name = $function($row); // Add the element in the tree. $target = &$trees[$region_name]['']; foreach ($parents[$name] as $key) { $target = &$target['children'][$key]; } $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']); // Add tabledrag indentation to the first row cell. if ($depth = count($parents[$name])) { $children = element_children($row); $cell = current($children); $row[$cell]['#prefix'] = theme('indentation', array('size' => $depth)) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : ''); } // Add row id and associate JS settings. $id = drupal_html_class($name); $row['#attributes']['id'] = $id; if (isset($row['#js_settings'])) { $row['#js_settings'] += array( 'rowHandler' => $row['#row_type'], 'name' => $name, 'region' => $region_name, ); $js_settings[$id] = $row['#js_settings']; } } } } // Determine rendering order from the tree structure. foreach ($regions as $region_name => $region) { $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], '_field_ui_reduce_order'); } $elements['#attached']['js'][] = array( 'type' => 'setting', 'data' => array('fieldUIRowsData' => $js_settings), ); return $elements; } /** * Returns HTML for Field UI overview tables. * * @param $variables * An associative array containing: * - elements: An associative array containing a Form API structure to be * rendered as a table. * * @ingroup themeable */ function theme_field_ui_table($variables) { $elements = $variables['elements']; $table = array(); $js_settings = array(); // Add table headers and attributes. foreach (array('header', 'attributes') as $key) { if (isset($elements["#$key"])) { $table[$key] = $elements["#$key"]; } } // Determine the colspan to use for region rows, by checking the number of // columns in the headers. $columns_count = 0; foreach ($table['header'] as $header) { $columns_count += (is_array($header) && isset($header['colspan']) ? $header['colspan'] : 1); } // Render rows, region by region. foreach ($elements['#regions'] as $region_name => $region) { $region_name_class = drupal_html_class($region_name); // Add region rows. if (isset($region['title'])) { $table['rows'][] = array( 'class' => array('region-title', 'region-' . $region_name_class . '-title'), 'no_striping' => TRUE, 'data' => array( array('data' => $region['title'], 'colspan' => $columns_count), ), ); } if (isset($region['message'])) { $class = (empty($region['rows_order']) ? 'region-empty' : 'region-populated'); $table['rows'][] = array( 'class' => array('region-message', 'region-' . $region_name_class . '-message', $class), 'no_striping' => TRUE, 'data' => array( array('data' => $region['message'], 'colspan' => $columns_count), ), ); } // Add form rows, in the order determined at pre-render time. foreach ($region['rows_order'] as $name) { $element = $elements[$name]; $row = array('data' => array()); if (isset($element['#attributes'])) { $row += $element['#attributes']; } // Render children as table cells. foreach (element_children($element) as $cell_key) { $child = &$element[$cell_key]; // Do not render a cell for children of #type 'value'. if (!(isset($child['#type']) && $child['#type'] == 'value')) { $cell = array('data' => drupal_render($child)); if (isset($child['#cell_attributes'])) { $cell += $child['#cell_attributes']; } $row['data'][] = $cell; } } $table['rows'][] = $row; } } return theme('table', $table); } /** * Form constructor for the 'Manage fields' form of a bundle. * * Allows fields and pseudo-fields to be re-ordered. * * @see field_ui_field_overview_form_validate() * @see field_ui_field_overview_form_submit() * @ingroup forms */ function field_ui_field_overview_form($form, &$form_state, $entity_type, $bundle) { $bundle = field_extract_bundle($entity_type, $bundle); field_ui_inactive_message($entity_type, $bundle); $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); // When displaying the form, make sure the list of fields is up-to-date. if (empty($form_state['post'])) { field_info_cache_clear(); } // Gather bundle information. $instances = field_info_instances($entity_type, $bundle); $field_types = field_info_field_types(); $widget_types = field_info_widget_types(); $extra_fields = field_info_extra_fields($entity_type, $bundle, 'form'); $form += array( '#entity_type' => $entity_type, '#bundle' => $bundle, '#fields' => array_keys($instances), '#extra' => array_keys($extra_fields), ); $table = array( '#type' => 'field_ui_table', '#tree' => TRUE, '#header' => array( t('Label'), t('Weight'), t('Parent'), t('Machine name'), t('Field type'), t('Widget'), array('data' => t('Operations'), 'colspan' => 2), ), '#parent_options' => array(), '#regions' => array( 'main' => array('message' => t('No fields are present yet.')), 'add_new' => array('title' => ' '), ), '#attributes' => array( 'class' => array('field-ui-overview'), 'id' => 'field-overview', ), ); // Fields. foreach ($instances as $name => $instance) { $field = field_info_field($instance['field_name']); $admin_field_path = $admin_path . '/fields/' . $instance['field_name']; $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')), '#row_type' => 'field', '#region_callback' => 'field_ui_field_overview_row_region', 'label' => array( '#markup' => check_plain($instance['label']), ), 'weight' => array( '#type' => 'textfield', '#title' => t('Weight for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#default_value' => $instance['widget']['weight'], '#size' => 3, '#attributes' => array('class' => array('field-weight')), ), 'parent_wrapper' => array( 'parent' => array( '#type' => 'select', '#title' => t('Parent for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#options' => $table['#parent_options'], '#empty_value' => '', '#attributes' => array('class' => array('field-parent')), '#parents' => array('fields', $name, 'parent'), ), 'hidden_name' => array( '#type' => 'hidden', '#default_value' => $name, '#attributes' => array('class' => array('field-name')), ), ), 'field_name' => array( '#markup' => $instance['field_name'], ), 'type' => array( '#type' => 'link', '#title' => t($field_types[$field['type']]['label']), '#href' => $admin_field_path . '/field-settings', '#options' => array('attributes' => array('title' => t('Edit field settings.'))), ), 'widget_type' => array( '#type' => 'link', '#title' => t($widget_types[$instance['widget']['type']]['label']), '#href' => $admin_field_path . '/widget-type', '#options' => array('attributes' => array('title' => t('Change widget type.'))), ), 'edit' => array( '#type' => 'link', '#title' => t('edit'), '#href' => $admin_field_path, '#options' => array('attributes' => array('title' => t('Edit instance settings.'))), ), 'delete' => array( '#type' => 'link', '#title' => t('delete'), '#href' => $admin_field_path . '/delete', '#options' => array('attributes' => array('title' => t('Delete instance.'))), ), ); if (!empty($instance['locked'])) { $table[$name]['edit'] = array('#value' => t('Locked')); $table[$name]['delete'] = array(); $table[$name]['#attributes']['class'][] = 'menu-disabled'; } } // Non-field elements. foreach ($extra_fields as $name => $extra_field) { $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')), '#row_type' => 'extra_field', '#region_callback' => 'field_ui_field_overview_row_region', 'label' => array( '#markup' => check_plain($extra_field['label']), ), 'weight' => array( '#type' => 'textfield', '#default_value' => $extra_field['weight'], '#size' => 3, '#attributes' => array('class' => array('field-weight')), '#title_display' => 'invisible', '#title' => t('Weight for @title', array('@title' => $extra_field['label'])), ), 'parent_wrapper' => array( 'parent' => array( '#type' => 'select', '#title' => t('Parent for @title', array('@title' => $extra_field['label'])), '#title_display' => 'invisible', '#options' => $table['#parent_options'], '#empty_value' => '', '#attributes' => array('class' => array('field-parent')), '#parents' => array('fields', $name, 'parent'), ), 'hidden_name' => array( '#type' => 'hidden', '#default_value' => $name, '#attributes' => array('class' => array('field-name')), ), ), 'field_name' => array( '#markup' => $name, ), 'type' => array( '#markup' => isset($extra_field['description']) ? $extra_field['description'] : '', '#cell_attributes' => array('colspan' => 2), ), 'edit' => array( '#markup' => isset($extra_field['edit']) ? $extra_field['edit'] : '', ), 'delete' => array( '#markup' => isset($extra_field['delete']) ? $extra_field['delete'] : '', ), ); } // Additional row: add new field. $max_weight = field_info_max_weight($entity_type, $bundle, 'form'); $field_type_options = field_ui_field_type_options(); $widget_type_options = field_ui_widget_type_options(NULL, TRUE); if ($field_type_options && $widget_type_options) { $name = '_add_new_field'; $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf', 'add-new')), '#row_type' => 'add_new_field', '#region_callback' => 'field_ui_field_overview_row_region', 'label' => array( '#type' => 'textfield', '#title' => t('New field label'), '#title_display' => 'invisible', '#size' => 15, '#description' => t('Label'), '#prefix' => '
      ' . t('Add new field') .'
      ', '#suffix' => '
      ', ), 'weight' => array( '#type' => 'textfield', '#default_value' => $max_weight + 1, '#size' => 3, '#title_display' => 'invisible', '#title' => t('Weight for new field'), '#attributes' => array('class' => array('field-weight')), '#prefix' => '
       
      ', ), 'parent_wrapper' => array( 'parent' => array( '#type' => 'select', '#title' => t('Parent for new field'), '#title_display' => 'invisible', '#options' => $table['#parent_options'], '#empty_value' => '', '#attributes' => array('class' => array('field-parent')), '#prefix' => '
       
      ', '#parents' => array('fields', $name, 'parent'), ), 'hidden_name' => array( '#type' => 'hidden', '#default_value' => $name, '#attributes' => array('class' => array('field-name')), ), ), 'field_name' => array( '#type' => 'machine_name', '#title' => t('New field name'), '#title_display' => 'invisible', // This field should stay LTR even for RTL languages. '#field_prefix' => 'field_', '#field_suffix' => '‎', '#size' => 15, '#description' => t('A unique machine-readable name containing letters, numbers, and underscores.'), // 32 characters minus the 'field_' prefix. '#maxlength' => 26, '#prefix' => '
       
      ', '#machine_name' => array( 'source' => array('fields', $name, 'label'), 'exists' => '_field_ui_field_name_exists', 'standalone' => TRUE, 'label' => '', ), '#required' => FALSE, ), 'type' => array( '#type' => 'select', '#title' => t('Type of new field'), '#title_display' => 'invisible', '#options' => $field_type_options, '#empty_option' => t('- Select a field type -'), '#description' => t('Type of data to store.'), '#attributes' => array('class' => array('field-type-select')), '#prefix' => '
       
      ', ), 'widget_type' => array( '#type' => 'select', '#title' => t('Widget for new field'), '#title_display' => 'invisible', '#options' => $widget_type_options, '#empty_option' => t('- Select a widget -'), '#description' => t('Form element to edit the data.'), '#attributes' => array('class' => array('widget-type-select')), '#cell_attributes' => array('colspan' => 3), '#prefix' => '
       
      ', ), // Place the 'translatable' property as an explicit value so that contrib // modules can form_alter() the value for newly created fields. 'translatable' => array( '#type' => 'value', '#value' => FALSE, ), ); } // Additional row: add existing field. $existing_fields = field_ui_existing_field_options($entity_type, $bundle); if ($existing_fields && $widget_type_options) { // Build list of options. $existing_field_options = array(); foreach ($existing_fields as $field_name => $info) { $text = t('@type: @field (@label)', array( '@type' => $info['type_label'], '@label' => $info['label'], '@field' => $info['field'], )); $existing_field_options[$field_name] = truncate_utf8($text, 80, FALSE, TRUE); } asort($existing_field_options); $name = '_add_existing_field'; $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf', 'add-new')), '#row_type' => 'add_new_field', '#region_callback' => 'field_ui_field_overview_row_region', 'label' => array( '#type' => 'textfield', '#title' => t('Existing field label'), '#title_display' => 'invisible', '#size' => 15, '#description' => t('Label'), '#attributes' => array('class' => array('label-textfield')), '#prefix' => '
      ' . t('Add existing field') .'
      ', '#suffix' => '
      ', ), 'weight' => array( '#type' => 'textfield', '#default_value' => $max_weight + 2, '#size' => 3, '#title_display' => 'invisible', '#title' => t('Weight for added field'), '#attributes' => array('class' => array('field-weight')), '#prefix' => '
       
      ', ), 'parent_wrapper' => array( 'parent' => array( '#type' => 'select', '#title' => t('Parent for existing field'), '#title_display' => 'invisible', '#options' => $table['#parent_options'], '#empty_value' => '', '#attributes' => array('class' => array('field-parent')), '#prefix' => '
       
      ', '#parents' => array('fields', $name, 'parent'), ), 'hidden_name' => array( '#type' => 'hidden', '#default_value' => $name, '#attributes' => array('class' => array('field-name')), ), ), 'field_name' => array( '#type' => 'select', '#title' => t('Existing field to share'), '#title_display' => 'invisible', '#options' => $existing_field_options, '#empty_option' => t('- Select an existing field -'), '#description' => t('Field to share'), '#attributes' => array('class' => array('field-select')), '#cell_attributes' => array('colspan' => 2), '#prefix' => '
       
      ', ), 'widget_type' => array( '#type' => 'select', '#title' => t('Widget for existing field'), '#title_display' => 'invisible', '#options' => $widget_type_options, '#empty_option' => t('- Select a widget -'), '#description' => t('Form element to edit the data.'), '#attributes' => array('class' => array('widget-type-select')), '#cell_attributes' => array('colspan' => 3), '#prefix' => '
       
      ', ), ); } $form['fields'] = $table; $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); $form['#attached']['css'][] = drupal_get_path('module', 'field_ui') . '/field_ui.css'; $form['#attached']['js'][] = drupal_get_path('module', 'field_ui') . '/field_ui.js'; // Add settings for the update selects behavior. $js_fields = array(); foreach ($existing_fields as $field_name => $info) { $js_fields[$field_name] = array('label' => $info['label'], 'type' => $info['type'], 'widget' => $info['widget_type']); } $form['#attached']['js'][] = array( 'type' => 'setting', 'data' => array('fields' => $js_fields, 'fieldWidgetTypes' => field_ui_widget_type_options()), ); // Add tabledrag behavior. $form['#attached']['drupal_add_tabledrag'][] = array('field-overview', 'order', 'sibling', 'field-weight'); $form['#attached']['drupal_add_tabledrag'][] = array('field-overview', 'match', 'parent', 'field-parent', 'field-parent', 'field-name'); return $form; } /** * Form validation handler for field_ui_field_overview_form(). * * @see field_ui_field_overview_form_submit() */ function field_ui_field_overview_form_validate($form, &$form_state) { _field_ui_field_overview_form_validate_add_new($form, $form_state); _field_ui_field_overview_form_validate_add_existing($form, $form_state); } /** * Validates the 'add new field' row of field_ui_field_overview_form(). * * @see field_ui_field_overview_form_validate() */ function _field_ui_field_overview_form_validate_add_new($form, &$form_state) { $field = $form_state['values']['fields']['_add_new_field']; // Validate if any information was provided in the 'add new field' row. if (array_filter(array($field['label'], $field['field_name'], $field['type'], $field['widget_type']))) { // Missing label. if (!$field['label']) { form_set_error('fields][_add_new_field][label', t('Add new field: you need to provide a label.')); } // Missing field name. if (!$field['field_name']) { form_set_error('fields][_add_new_field][field_name', t('Add new field: you need to provide a field name.')); } // Field name validation. else { $field_name = $field['field_name']; // Add the 'field_' prefix. $field_name = 'field_' . $field_name; form_set_value($form['fields']['_add_new_field']['field_name'], $field_name, $form_state); } // Missing field type. if (!$field['type']) { form_set_error('fields][_add_new_field][type', t('Add new field: you need to select a field type.')); } // Missing widget type. if (!$field['widget_type']) { form_set_error('fields][_add_new_field][widget_type', t('Add new field: you need to select a widget.')); } // Wrong widget type. elseif ($field['type']) { $widget_types = field_ui_widget_type_options($field['type']); if (!isset($widget_types[$field['widget_type']])) { form_set_error('fields][_add_new_field][widget_type', t('Add new field: invalid widget.')); } } } } /** * Render API callback: Checks if a field machine name is taken. * * @param $value * The machine name, not prefixed with 'field_'. * * @return * Whether or not the field machine name is taken. */ function _field_ui_field_name_exists($value) { // Prefix with 'field_'. $field_name = 'field_' . $value; // We need to check inactive fields as well, so we can't use // field_info_fields(). return (bool) field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE)); } /** * Validates the 'add existing field' row of field_ui_field_overview_form(). * * @see field_ui_field_overview_form_validate() */ function _field_ui_field_overview_form_validate_add_existing($form, &$form_state) { // The form element might be absent if no existing fields can be added to // this bundle. if (isset($form_state['values']['fields']['_add_existing_field'])) { $field = $form_state['values']['fields']['_add_existing_field']; // Validate if any information was provided in the 'add existing field' row. if (array_filter(array($field['label'], $field['field_name'], $field['widget_type']))) { // Missing label. if (!$field['label']) { form_set_error('fields][_add_existing_field][label', t('Add existing field: you need to provide a label.')); } // Missing existing field name. if (!$field['field_name']) { form_set_error('fields][_add_existing_field][field_name', t('Add existing field: you need to select a field.')); } // Missing widget type. if (!$field['widget_type']) { form_set_error('fields][_add_existing_field][widget_type', t('Add existing field: you need to select a widget.')); } // Wrong widget type. elseif ($field['field_name'] && ($existing_field = field_info_field($field['field_name']))) { $widget_types = field_ui_widget_type_options($existing_field['type']); if (!isset($widget_types[$field['widget_type']])) { form_set_error('fields][_add_existing_field][widget_type', t('Add existing field: invalid widget.')); } } } } } /** * Form submission handler for field_ui_field_overview_form(). * * @see field_ui_field_overview_form_validate() */ function field_ui_field_overview_form_submit($form, &$form_state) { $form_values = $form_state['values']['fields']; $entity_type = $form['#entity_type']; $bundle = $form['#bundle']; $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); $bundle_settings = field_bundle_settings($entity_type, $bundle); // Update field weights. foreach ($form_values as $key => $values) { if (in_array($key, $form['#fields'])) { $instance = field_read_instance($entity_type, $key, $bundle); $instance['widget']['weight'] = $values['weight']; field_update_instance($instance); } elseif (in_array($key, $form['#extra'])) { $bundle_settings['extra_fields']['form'][$key]['weight'] = $values['weight']; } } field_bundle_settings($entity_type, $bundle, $bundle_settings); $destinations = array(); // Create new field. $field = array(); if (!empty($form_values['_add_new_field']['field_name'])) { $values = $form_values['_add_new_field']; $field = array( 'field_name' => $values['field_name'], 'type' => $values['type'], 'translatable' => $values['translatable'], ); $instance = array( 'field_name' => $field['field_name'], 'entity_type' => $entity_type, 'bundle' => $bundle, 'label' => $values['label'], 'widget' => array( 'type' => $values['widget_type'], 'weight' => $values['weight'], ), ); // Create the field and instance. try { field_create_field($field); field_create_instance($instance); $destinations[] = $admin_path . '/fields/' . $field['field_name'] . '/field-settings'; $destinations[] = $admin_path . '/fields/' . $field['field_name']; // Store new field information for any additional submit handlers. $form_state['fields_added']['_add_new_field'] = $field['field_name']; } catch (Exception $e) { drupal_set_message(t('There was a problem creating field %label: !message', array('%label' => $instance['label'], '!message' => $e->getMessage())), 'error'); } } // Add existing field. if (!empty($form_values['_add_existing_field']['field_name'])) { $values = $form_values['_add_existing_field']; $field = field_info_field($values['field_name']); if (!empty($field['locked'])) { drupal_set_message(t('The field %label cannot be added because it is locked.', array('%label' => $values['label'])), 'error'); } else { $instance = array( 'field_name' => $field['field_name'], 'entity_type' => $entity_type, 'bundle' => $bundle, 'label' => $values['label'], 'widget' => array( 'type' => $values['widget_type'], 'weight' => $values['weight'], ), ); try { field_create_instance($instance); $destinations[] = $admin_path . '/fields/' . $instance['field_name'] . '/edit'; // Store new field information for any additional submit handlers. $form_state['fields_added']['_add_existing_field'] = $instance['field_name']; } catch (Exception $e) { drupal_set_message(t('There was a problem creating field instance %label: @message.', array('%label' => $instance['label'], '@message' => $e->getMessage())), 'error'); } } } if ($destinations) { $destination = drupal_get_destination(); $destinations[] = $destination['destination']; unset($_GET['destination']); $form_state['redirect'] = field_ui_get_destinations($destinations); } else { drupal_set_message(t('Your settings have been saved.')); } } /** * Form constructor for the field display settings for a given view mode. * * @see field_ui_display_overview_multistep_submit() * @see field_ui_display_overview_form_submit() * @ingroup forms */ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bundle, $view_mode) { $bundle = field_extract_bundle($entity_type, $bundle); field_ui_inactive_message($entity_type, $bundle); $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); // Gather type information. $instances = field_info_instances($entity_type, $bundle); $field_types = field_info_field_types(); $extra_fields = field_info_extra_fields($entity_type, $bundle, 'display'); $form_state += array( 'formatter_settings_edit' => NULL, ); $form += array( '#entity_type' => $entity_type, '#bundle' => $bundle, '#view_mode' => $view_mode, '#fields' => array_keys($instances), '#extra' => array_keys($extra_fields), ); if (empty($instances) && empty($extra_fields)) { drupal_set_message(t('There are no fields yet added. You can add new fields on the Manage fields page.', array('@link' => url($admin_path . '/fields'))), 'warning'); return $form; } $table = array( '#type' => 'field_ui_table', '#tree' => TRUE, '#header' => array( t('Field'), t('Weight'), t('Parent'), t('Label'), array('data' => t('Format'), 'colspan' => 3), ), '#regions' => array( 'visible' => array('message' => t('No field is displayed.')), 'hidden' => array('title' => t('Hidden'), 'message' => t('No field is hidden.')), ), '#parent_options' => array(), '#attributes' => array( 'class' => array('field-ui-overview'), 'id' => 'field-display-overview', ), // Add Ajax wrapper. '#prefix' => '
      ', '#suffix' => '
      ', ); $field_label_options = array( 'above' => t('Above'), 'inline' => t('Inline'), 'hidden' => t(''), ); $extra_visibility_options = array( 'visible' => t('Visible'), 'hidden' => t('Hidden'), ); // Field rows. foreach ($instances as $name => $instance) { $field = field_info_field($instance['field_name']); $display = $instance['display'][$view_mode]; $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')), '#row_type' => 'field', '#region_callback' => 'field_ui_display_overview_row_region', '#js_settings' => array( 'rowHandler' => 'field', 'defaultFormatter' => $field_types[$field['type']]['default_formatter'], ), 'human_name' => array( '#markup' => check_plain($instance['label']), ), 'weight' => array( '#type' => 'textfield', '#title' => t('Weight for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#default_value' => $display['weight'], '#size' => 3, '#attributes' => array('class' => array('field-weight')), ), 'parent_wrapper' => array( 'parent' => array( '#type' => 'select', '#title' => t('Label display for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#options' => $table['#parent_options'], '#empty_value' => '', '#attributes' => array('class' => array('field-parent')), '#parents' => array('fields', $name, 'parent'), ), 'hidden_name' => array( '#type' => 'hidden', '#default_value' => $name, '#attributes' => array('class' => array('field-name')), ), ), 'label' => array( '#type' => 'select', '#title' => t('Label display for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#options' => $field_label_options, '#default_value' => $display['label'], ), ); $formatter_options = field_ui_formatter_options($field['type']); $formatter_options['hidden'] = t(''); $table[$name]['format'] = array( 'type' => array( '#type' => 'select', '#title' => t('Formatter for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#options' => $formatter_options, '#default_value' => $display['type'], '#parents' => array('fields', $name, 'type'), '#attributes' => array('class' => array('field-formatter-type')), ), 'settings_edit_form' => array(), ); // Formatter settings. // Check the currently selected formatter, and merge persisted values for // formatter settings. if (isset($form_state['values']['fields'][$name]['type'])) { $formatter_type = $form_state['values']['fields'][$name]['type']; } else { $formatter_type = $display['type']; } if (isset($form_state['formatter_settings'][$name])) { $settings = $form_state['formatter_settings'][$name]; } else { $settings = $display['settings']; } $settings += field_info_formatter_settings($formatter_type); $instance['display'][$view_mode]['type'] = $formatter_type; $formatter = field_info_formatter_types($formatter_type); $instance['display'][$view_mode]['module'] = $formatter['module']; $instance['display'][$view_mode]['settings'] = $settings; // Base button element for the various formatter settings actions. $base_button = array( '#submit' => array('field_ui_display_overview_multistep_submit'), '#ajax' => array( 'callback' => 'field_ui_display_overview_multistep_js', 'wrapper' => 'field-display-overview-wrapper', 'effect' => 'fade', ), '#field_name' => $name, ); if ($form_state['formatter_settings_edit'] == $name) { // We are currently editing this field's formatter settings. Display the // settings form and submit buttons. $table[$name]['format']['settings_edit_form'] = array(); $settings_form = array(); $function = $formatter['module'] . '_field_formatter_settings_form'; if (function_exists($function)) { $settings_form = $function($field, $instance, $view_mode, $form, $form_state); } if ($settings_form) { $table[$name]['format']['#cell_attributes'] = array('colspan' => 3); $table[$name]['format']['settings_edit_form'] = array( '#type' => 'container', '#attributes' => array('class' => array('field-formatter-settings-edit-form')), '#parents' => array('fields', $name, 'settings_edit_form'), 'label' => array( '#markup' => t('Format settings:') . ' ' . $formatter['label'] . '', ), 'settings' => $settings_form, 'actions' => array( '#type' => 'actions', 'save_settings' => $base_button + array( '#type' => 'submit', '#name' => $name . '_formatter_settings_update', '#value' => t('Update'), '#op' => 'update', ), 'cancel_settings' => $base_button + array( '#type' => 'submit', '#name' => $name . '_formatter_settings_cancel', '#value' => t('Cancel'), '#op' => 'cancel', // Do not check errors for the 'Cancel' button, but make sure we // get the value of the 'formatter type' select. '#limit_validation_errors' => array(array('fields', $name, 'type')), ), ), ); $table[$name]['#attributes']['class'][] = 'field-formatter-settings-editing'; } } else { // Display a summary of the current formatter settings. $summary = module_invoke($formatter['module'], 'field_formatter_settings_summary', $field, $instance, $view_mode); $table[$name]['settings_summary'] = array(); $table[$name]['settings_edit'] = array(); if ($summary) { $table[$name]['settings_summary'] = array( '#markup' => '
      ' . $summary . '
      ', '#cell_attributes' => array('class' => array('field-formatter-summary-cell')), ); $table[$name]['settings_edit'] = $base_button + array( '#type' => 'image_button', '#name' => $name . '_formatter_settings_edit', '#src' => 'misc/configure.png', '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')), '#op' => 'edit', // Do not check errors for the 'Edit' button, but make sure we get // the value of the 'formatter type' select. '#limit_validation_errors' => array(array('fields', $name, 'type')), '#prefix' => '
      ', '#suffix' => '
      ', ); } } } // Non-field elements. foreach ($extra_fields as $name => $extra_field) { $display = $extra_field['display'][$view_mode]; $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')), '#row_type' => 'extra_field', '#region_callback' => 'field_ui_display_overview_row_region', '#js_settings' => array('rowHandler' => 'field'), 'human_name' => array( '#markup' => check_plain($extra_field['label']), ), 'weight' => array( '#type' => 'textfield', '#title' => t('Weight for @title', array('@title' => $extra_field['label'])), '#title_display' => 'invisible', '#default_value' => $display['weight'], '#size' => 3, '#attributes' => array('class' => array('field-weight')), ), 'parent_wrapper' => array( 'parent' => array( '#type' => 'select', '#title' => t('Parents for @title', array('@title' => $extra_field['label'])), '#title_display' => 'invisible', '#options' => $table['#parent_options'], '#empty_value' => '', '#attributes' => array('class' => array('field-parent')), '#parents' => array('fields', $name, 'parent'), ), 'hidden_name' => array( '#type' => 'hidden', '#default_value' => $name, '#attributes' => array('class' => array('field-name')), ), ), 'empty_cell' => array( '#markup' => ' ', ), 'format' => array( 'type' => array( '#type' => 'select', '#title' => t('Visibility for @title', array('@title' => $extra_field['label'])), '#title_display' => 'invisible', '#options' => $extra_visibility_options, '#default_value' => $display['visible'] ? 'visible' : 'hidden', '#parents' => array('fields', $name, 'type'), '#attributes' => array('class' => array('field-formatter-type')), ), ), 'settings_summary' => array(), 'settings_edit' => array(), ); } $form['fields'] = $table; // Custom display settings. if ($view_mode == 'default') { $form['modes'] = array( '#type' => 'fieldset', '#title' => t('Custom display settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); // Collect options and default values for the 'Custom display settings' // checkboxes. $options = array(); $default = array(); $entity_info = entity_get_info($entity_type); $view_modes = $entity_info['view modes']; $view_mode_settings = field_view_mode_settings($entity_type, $bundle); foreach ($view_modes as $view_mode_name => $view_mode_info) { $options[$view_mode_name] = $view_mode_info['label']; if (!empty($view_mode_settings[$view_mode_name]['custom_settings'])) { $default[] = $view_mode_name; } } $form['modes']['view_modes_custom'] = array( '#type' => 'checkboxes', '#title' => t('Use custom display settings for the following view modes'), '#options' => $options, '#default_value' => $default, ); } // In overviews involving nested rows from contributed modules (i.e // field_group), the 'format type' selects can trigger a series of changes in // child rows. The #ajax behavior is therefore not attached directly to the // selects, but triggered by the client-side script through a hidden #ajax // 'Refresh' button. A hidden 'refresh_rows' input tracks the name of // affected rows. $form['refresh_rows'] = array('#type' => 'hidden'); $form['refresh'] = array( '#type' => 'submit', '#value' => t('Refresh'), '#op' => 'refresh_table', '#submit' => array('field_ui_display_overview_multistep_submit'), '#ajax' => array( 'callback' => 'field_ui_display_overview_multistep_js', 'wrapper' => 'field-display-overview-wrapper', 'effect' => 'fade', // The button stays hidden, so we hide the Ajax spinner too. Ad-hoc // spinners will be added manually by the client-side script. 'progress' => 'none', ), ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); $form['#attached']['js'][] = drupal_get_path('module', 'field_ui') . '/field_ui.js'; $form['#attached']['css'][] = drupal_get_path('module', 'field_ui') . '/field_ui.css'; // Add tabledrag behavior. $form['#attached']['drupal_add_tabledrag'][] = array('field-display-overview', 'order', 'sibling', 'field-weight'); $form['#attached']['drupal_add_tabledrag'][] = array('field-display-overview', 'match', 'parent', 'field-parent', 'field-parent', 'field-name'); return $form; } /** * Form submission handler for buttons in field_ui_display_overview_form(). */ function field_ui_display_overview_multistep_submit($form, &$form_state) { $trigger = $form_state['triggering_element']; $op = $trigger['#op']; switch ($op) { case 'edit': // Store the field whose settings are currently being edited. $field_name = $trigger['#field_name']; $form_state['formatter_settings_edit'] = $field_name; break; case 'update': // Store the saved settings, and set the field back to 'non edit' mode. $field_name = $trigger['#field_name']; $values = $form_state['values']['fields'][$field_name]['settings_edit_form']['settings']; $form_state['formatter_settings'][$field_name] = $values; unset($form_state['formatter_settings_edit']); break; case 'cancel': // Set the field back to 'non edit' mode. unset($form_state['formatter_settings_edit']); break; case 'refresh_table': // If the currently edited field is one of the rows to be refreshed, set // it back to 'non edit' mode. $updated_rows = explode(' ', $form_state['values']['refresh_rows']); if (isset($form_state['formatter_settings_edit']) && in_array($form_state['formatter_settings_edit'], $updated_rows)) { unset($form_state['formatter_settings_edit']); } break; } $form_state['rebuild'] = TRUE; } /** * Ajax handler for multistep buttons on the 'Manage display' screen. */ function field_ui_display_overview_multistep_js($form, &$form_state) { $trigger = $form_state['triggering_element']; $op = $trigger['#op']; // Pick the elements that need to receive the ajax-new-content effect. switch ($op) { case 'edit': $updated_rows = array($trigger['#field_name']); $updated_columns = array('format'); break; case 'update': case 'cancel': $updated_rows = array($trigger['#field_name']); $updated_columns = array('format', 'settings_summary', 'settings_edit'); break; case 'refresh_table': $updated_rows = array_values(explode(' ', $form_state['values']['refresh_rows'])); $updated_columns = array('settings_summary', 'settings_edit'); break; } foreach ($updated_rows as $name) { foreach ($updated_columns as $key) { $element = &$form['fields'][$name][$key]; $element['#prefix'] = '
      ' . (isset($element['#prefix']) ? $element['#prefix'] : ''); $element['#suffix'] = (isset($element['#suffix']) ? $element['#suffix'] : '') . '
      '; } } // Return the whole table. return $form['fields']; } /** * Form submission handler for field_ui_display_overview_form(). */ function field_ui_display_overview_form_submit($form, &$form_state) { $form_values = $form_state['values']; $entity_type = $form['#entity_type']; $bundle = $form['#bundle']; $view_mode = $form['#view_mode']; // Save data for 'regular' fields. foreach ($form['#fields'] as $field_name) { // Retrieve the stored instance settings to merge with the incoming values. $instance = field_read_instance($entity_type, $field_name, $bundle); $values = $form_values['fields'][$field_name]; // Get formatter settings. They lie either directly in submitted form // values (if the whole form was submitted while some formatter // settings were being edited), or have been persisted in // $form_state. $settings = array(); if (isset($values['settings_edit_form']['settings'])) { $settings = $values['settings_edit_form']['settings']; } elseif (isset($form_state['formatter_settings'][$field_name])) { $settings = $form_state['formatter_settings'][$field_name]; } elseif (isset($instance['display'][$view_mode]['settings'])) { $settings = $instance['display'][$view_mode]['settings']; } // Only save settings actually used by the selected formatter. $default_settings = field_info_formatter_settings($values['type']); $settings = array_intersect_key($settings, $default_settings); $instance['display'][$view_mode] = array( 'label' => $values['label'], 'type' => $values['type'], 'weight' => $values['weight'], 'settings' => $settings, ); field_update_instance($instance); } // Get current bundle settings. $bundle_settings = field_bundle_settings($entity_type, $bundle); // Save data for 'extra' fields. foreach ($form['#extra'] as $name) { $bundle_settings['extra_fields']['display'][$name][$view_mode] = array( 'weight' => $form_values['fields'][$name]['weight'], 'visible' => $form_values['fields'][$name]['type'] == 'visible', ); } // Save view modes data. if ($view_mode == 'default') { $entity_info = entity_get_info($entity_type); foreach ($form_values['view_modes_custom'] as $view_mode_name => $value) { // Display a message for each view mode newly configured to use custom // settings. $view_mode_settings = field_view_mode_settings($entity_type, $bundle); if (!empty($value) && empty($view_mode_settings[$view_mode_name]['custom_settings'])) { $view_mode_label = $entity_info['view modes'][$view_mode_name]['label']; $path = _field_ui_bundle_admin_path($entity_type, $bundle) . "/display/$view_mode_name"; drupal_set_message(t('The %view_mode mode now uses custom display settings. You might want to configure them.', array('%view_mode' => $view_mode_label, '@url' => url($path)))); // Initialize the newly customized view mode with the display settings // from the default view mode. _field_ui_add_default_view_mode_settings($entity_type, $bundle, $view_mode_name, $bundle_settings); } $bundle_settings['view_modes'][$view_mode_name]['custom_settings'] = !empty($value); } } // Save updated bundle settings. field_bundle_settings($entity_type, $bundle, $bundle_settings); drupal_set_message(t('Your settings have been saved.')); } /** * Populates display settings for a new view mode from the default view mode. * * When an administrator decides to use custom display settings for a view mode, * that view mode needs to be initialized with the display settings for the * 'default' view mode, which it was previously using. This helper function * adds the new custom display settings to this bundle's instances, and saves * them. It also modifies the passed-in $settings array, which the caller can * then save using field_bundle_settings(). * * @param $entity_type * The bundle's entity type. * @param $bundle * The bundle whose view mode is being customized. * @param $view_mode * The view mode that the administrator has set to use custom settings. * @param $settings * An associative array of bundle settings, as expected by * field_bundle_settings(). * * @see field_ui_display_overview_form_submit(). * @see field_bundle_settings() */ function _field_ui_add_default_view_mode_settings($entity_type, $bundle, $view_mode, &$settings) { // Update display settings for field instances. $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle)); foreach ($instances as $instance) { // If this field instance has display settings defined for this view mode, // respect those settings. if (!isset($instance['display'][$view_mode])) { // The instance doesn't specify anything for this view mode, so use the // default display settings. $instance['display'][$view_mode] = $instance['display']['default']; field_update_instance($instance); } } // Update display settings for 'extra fields'. foreach (array_keys($settings['extra_fields']['display']) as $name) { if (!isset($settings['extra_fields']['display'][$name][$view_mode])) { $settings['extra_fields']['display'][$name][$view_mode] = $settings['extra_fields']['display'][$name]['default']; } } } /** * Returns an array of field_type options. */ function field_ui_field_type_options() { $options = &drupal_static(__FUNCTION__); if (!isset($options)) { $options = array(); $field_types = field_info_field_types(); $field_type_options = array(); foreach ($field_types as $name => $field_type) { // Skip field types which have no widget types, or should not be add via // uesr interface. if (field_ui_widget_type_options($name) && empty($field_type['no_ui'])) { $options[$name] = $field_type['label']; } } asort($options); } return $options; } /** * Returns an array of widget type options for a field type. * * If no field type is provided, returns a nested array of all widget types, * keyed by field type human name. */ function field_ui_widget_type_options($field_type = NULL, $by_label = FALSE) { $options = &drupal_static(__FUNCTION__); if (!isset($options)) { $options = array(); $field_types = field_info_field_types(); foreach (field_info_widget_types() as $name => $widget_type) { foreach ($widget_type['field types'] as $widget_field_type) { // Check that the field type exists. if (isset($field_types[$widget_field_type])) { $options[$widget_field_type][$name] = $widget_type['label']; } } } } if (isset($field_type)) { return !empty($options[$field_type]) ? $options[$field_type] : array(); } if ($by_label) { $field_types = field_info_field_types(); $options_by_label = array(); foreach ($options as $field_type => $widgets) { $options_by_label[$field_types[$field_type]['label']] = $widgets; } return $options_by_label; } return $options; } /** * Returns an array of formatter options for a field type. * * If no field type is provided, returns a nested array of all formatters, keyed * by field type. */ function field_ui_formatter_options($field_type = NULL) { $options = &drupal_static(__FUNCTION__); if (!isset($options)) { $field_types = field_info_field_types(); $options = array(); foreach (field_info_formatter_types() as $name => $formatter) { foreach ($formatter['field types'] as $formatter_field_type) { // Check that the field type exists. if (isset($field_types[$formatter_field_type])) { $options[$formatter_field_type][$name] = $formatter['label']; } } } } if ($field_type) { return !empty($options[$field_type]) ? $options[$field_type] : array(); } return $options; } /** * Returns an array of existing fields to be added to a bundle. */ function field_ui_existing_field_options($entity_type, $bundle) { $info = array(); $field_types = field_info_field_types(); foreach (field_info_instances() as $existing_entity_type => $bundles) { foreach ($bundles as $existing_bundle => $instances) { // No need to look in the current bundle. if (!($existing_bundle == $bundle && $existing_entity_type == $entity_type)) { foreach ($instances as $instance) { $field = field_info_field($instance['field_name']); // Don't show // - locked fields, // - fields already in the current bundle, // - fields that cannot be added to the entity type, // - fields that should not be added via user interface. if (empty($field['locked']) && !field_info_instance($entity_type, $field['field_name'], $bundle) && (empty($field['entity_types']) || in_array($entity_type, $field['entity_types'])) && empty($field_types[$field['type']]['no_ui'])) { $info[$instance['field_name']] = array( 'type' => $field['type'], 'type_label' => $field_types[$field['type']]['label'], 'field' => $field['field_name'], 'label' => $instance['label'], 'widget_type' => $instance['widget']['type'], ); } } } } } return $info; } /** * Form constructor for the field settings edit page. * * @see field_ui_field_settings_form_submit() * @ingroup forms */ function field_ui_field_settings_form($form, &$form_state, $instance) { $bundle = $instance['bundle']; $entity_type = $instance['entity_type']; $field = field_info_field($instance['field_name']); drupal_set_title($instance['label']); $description = '

      ' . t('These settings apply to the %field field everywhere it is used. These settings impact the way that data is stored in the database and cannot be changed once data has been created.', array('%field' => $instance['label'])) . '

      '; // Create a form structure for the field values. $form['field'] = array( '#type' => 'fieldset', '#title' => t('Field settings'), '#description' => $description, '#tree' => TRUE, ); // See if data already exists for this field. // If so, prevent changes to the field settings. $has_data = field_has_data($field); if ($has_data) { $form['field']['#description'] = '
      ' . t('There is data for this field in the database. The field settings can no longer be changed.') . '
      ' . $form['field']['#description']; } // Build the non-configurable field values. $form['field']['field_name'] = array('#type' => 'value', '#value' => $field['field_name']); $form['field']['type'] = array('#type' => 'value', '#value' => $field['type']); $form['field']['module'] = array('#type' => 'value', '#value' => $field['module']); $form['field']['active'] = array('#type' => 'value', '#value' => $field['active']); // Add settings provided by the field module. The field module is // responsible for not returning settings that cannot be changed if // the field already has data. $form['field']['settings'] = array(); $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance, $has_data); if (is_array($additions)) { $form['field']['settings'] = $additions; } if (empty($form['field']['settings'])) { $form['field']['settings'] = array( '#markup' => t('%field has no field settings.', array('%field' => $instance['label'])), ); } $form['#entity_type'] = $entity_type; $form['#bundle'] = $bundle; $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save field settings')); return $form; } /** * Form submission handler for field_ui_field_settings_form(). */ function field_ui_field_settings_form_submit($form, &$form_state) { $form_values = $form_state['values']; $field_values = $form_values['field']; // Merge incoming form values into the existing field. $field = field_info_field($field_values['field_name']); $entity_type = $form['#entity_type']; $bundle = $form['#bundle']; $instance = field_info_instance($entity_type, $field['field_name'], $bundle); // Update the field. $field = array_merge($field, $field_values); try { field_update_field($field); drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label']))); $form_state['redirect'] = field_ui_next_destination($entity_type, $bundle); } catch (Exception $e) { drupal_set_message(t('Attempt to update field %label failed: %message.', array('%label' => $instance['label'], '%message' => $e->getMessage())), 'error'); } } /** * Form constructor for the widget selection form. * * Path: BUNDLE_ADMIN_PATH/fields/%field/widget-type, where BUNDLE_ADMIN_PATH is * the path stored in the ['admin']['info'] property in the return value of * hook_entity_info(). * * @see field_ui_menu() * @see field_ui_widget_type_form_submit() * @ingroup forms */ function field_ui_widget_type_form($form, &$form_state, $instance) { drupal_set_title($instance['label']); $bundle = $instance['bundle']; $entity_type = $instance['entity_type']; $field_name = $instance['field_name']; $field = field_info_field($field_name); $field_type = field_info_field_types($field['type']); $widget_type = field_info_widget_types($instance['widget']['type']); $bundles = field_info_bundles(); $bundle_label = $bundles[$entity_type][$bundle]['label']; $form = array( '#bundle' => $bundle, '#entity_type' => $entity_type, '#field_name' => $field_name, ); $form['basic'] = array( '#type' => 'fieldset', '#title' => t('Change widget'), ); $form['basic']['widget_type'] = array( '#type' => 'select', '#title' => t('Widget type'), '#required' => TRUE, '#options' => field_ui_widget_type_options($field['type']), '#default_value' => $instance['widget']['type'], '#description' => t('The type of form element you would like to present to the user when creating this field in the %type type.', array('%type' => $bundle_label)), ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Continue')); $form['#validate'] = array(); $form['#submit'] = array('field_ui_widget_type_form_submit'); return $form; } /** * Form submission handler for field_ui_widget_type_form(). */ function field_ui_widget_type_form_submit($form, &$form_state) { $form_values = $form_state['values']; $bundle = $form['#bundle']; $entity_type = $form['#entity_type']; $field_name = $form['#field_name']; // Retrieve the stored instance settings to merge with the incoming values. $instance = field_read_instance($entity_type, $field_name, $bundle); // Set the right module information. $widget_type = field_info_widget_types($form_values['widget_type']); $widget_module = $widget_type['module']; $instance['widget']['type'] = $form_values['widget_type']; $instance['widget']['module'] = $widget_module; try { field_update_instance($instance); drupal_set_message(t('Changed the widget for field %label.', array('%label' => $instance['label']))); } catch (Exception $e) { drupal_set_message(t('There was a problem changing the widget for field %label.', array('%label' => $instance['label'])), 'error'); } $form_state['redirect'] = field_ui_next_destination($entity_type, $bundle); } /** * Form constructor for removing a field instance from a bundle. * * @see field_ui_field_delete_form_submit() * @ingroup forms */ function field_ui_field_delete_form($form, &$form_state, $instance) { $bundle = $instance['bundle']; $entity_type = $instance['entity_type']; $field = field_info_field($instance['field_name']); $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); $form['entity_type'] = array('#type' => 'value', '#value' => $entity_type); $form['bundle'] = array('#type' => 'value', '#value' => $bundle); $form['field_name'] = array('#type' => 'value', '#value' => $field['field_name']); $output = confirm_form($form, t('Are you sure you want to delete the field %field?', array('%field' => $instance['label'])), $admin_path . '/fields', t('If you have any content left in this field, it will be lost. This action cannot be undone.'), t('Delete'), t('Cancel'), 'confirm' ); if ($field['locked']) { unset($output['actions']['submit']); $output['description']['#markup'] = t('This field is locked and cannot be deleted.'); } return $output; } /** * Form submission handler for field_ui_field_delete_form(). * * Removes a field instance from a bundle. If the field has no more instances, * it will be marked as deleted too. */ function field_ui_field_delete_form_submit($form, &$form_state) { $form_values = $form_state['values']; $field_name = $form_values['field_name']; $bundle = $form_values['bundle']; $entity_type = $form_values['entity_type']; $field = field_info_field($field_name); $instance = field_info_instance($entity_type, $field_name, $bundle); $bundles = field_info_bundles(); $bundle_label = $bundles[$entity_type][$bundle]['label']; if (!empty($bundle) && $field && !$field['locked'] && $form_values['confirm']) { field_delete_instance($instance); drupal_set_message(t('The field %field has been deleted from the %type content type.', array('%field' => $instance['label'], '%type' => $bundle_label))); } else { drupal_set_message(t('There was a problem removing the %field from the %type content type.', array('%field' => $instance['label'], '%type' => $bundle_label)), 'error'); } $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); $form_state['redirect'] = field_ui_get_destinations(array($admin_path . '/fields')); // Fields are purged on cron. However field module prevents disabling modules // when field types they provided are used in a field until it is fully // purged. In the case that a field has minimal or no content, a single call // to field_purge_batch() will remove it from the system. Call this with a // low batch limit to avoid administrators having to wait for cron runs when // removing instances that meet this criteria. field_purge_batch(10); } /** * Form constructor for the field instance settings form. * * @see field_ui_field_edit_form_validate() * @see field_ui_field_edit_form_submit() * @ingroup forms */ function field_ui_field_edit_form($form, &$form_state, $instance) { $bundle = $instance['bundle']; $entity_type = $instance['entity_type']; $field = field_info_field($instance['field_name']); drupal_set_title($instance['label']); $form['#field'] = $field; $form['#instance'] = $instance; if (!empty($field['locked'])) { $form['locked'] = array( '#markup' => t('The field %field is locked and cannot be edited.', array('%field' => $instance['label'])), ); return $form; } $field_type = field_info_field_types($field['type']); $widget_type = field_info_widget_types($instance['widget']['type']); $bundles = field_info_bundles(); // Create a form structure for the instance values. $form['instance'] = array( '#tree' => TRUE, '#type' => 'fieldset', '#title' => t('%type settings', array('%type' => $bundles[$entity_type][$bundle]['label'])), '#description' => t('These settings apply only to the %field field when used in the %type type.', array( '%field' => $instance['label'], '%type' => $bundles[$entity_type][$bundle]['label'], )), // Ensure field_ui_field_edit_instance_pre_render() gets called in addition // to, not instead of, the #pre_render function(s) needed by all fieldsets. '#pre_render' => array_merge(array('field_ui_field_edit_instance_pre_render'), element_info_property('fieldset', '#pre_render', array())), ); // Build the non-configurable instance values. $form['instance']['field_name'] = array( '#type' => 'value', '#value' => $instance['field_name'], ); $form['instance']['entity_type'] = array( '#type' => 'value', '#value' => $entity_type, ); $form['instance']['bundle'] = array( '#type' => 'value', '#value' => $bundle, ); $form['instance']['widget']['weight'] = array( '#type' => 'value', '#value' => !empty($instance['widget']['weight']) ? $instance['widget']['weight'] : 0, ); // Build the configurable instance values. $form['instance']['label'] = array( '#type' => 'textfield', '#title' => t('Label'), '#default_value' => !empty($instance['label']) ? $instance['label'] : $field['field_name'], '#required' => TRUE, '#weight' => -20, ); $form['instance']['required'] = array( '#type' => 'checkbox', '#title' => t('Required field'), '#default_value' => !empty($instance['required']), '#weight' => -10, ); $form['instance']['description'] = array( '#type' => 'textarea', '#title' => t('Help text'), '#default_value' => !empty($instance['description']) ? $instance['description'] : '', '#rows' => 5, '#description' => t('Instructions to present to the user below this field on the editing form.
      Allowed HTML tags: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())), '#weight' => -5, ); // Build the widget component of the instance. $form['instance']['widget']['type'] = array( '#type' => 'value', '#value' => $instance['widget']['type'], ); $form['instance']['widget']['module'] = array( '#type' => 'value', '#value' => $widget_type['module'], ); $form['instance']['widget']['active'] = array( '#type' => 'value', '#value' => !empty($field['instance']['widget']['active']) ? 1 : 0, ); // Add additional field instance settings from the field module. $additions = module_invoke($field['module'], 'field_instance_settings_form', $field, $instance); if (is_array($additions)) { $form['instance']['settings'] = $additions; } // Add additional widget settings from the widget module. $additions = module_invoke($widget_type['module'], 'field_widget_settings_form', $field, $instance); if (is_array($additions)) { $form['instance']['widget']['settings'] = $additions; $form['instance']['widget']['active']['#value'] = 1; } // Add handling for default value if not provided by any other module. if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && empty($instance['default_value_function'])) { $form['instance']['default_value_widget'] = field_ui_default_value_widget($field, $instance, $form, $form_state); } $has_data = field_has_data($field); if ($has_data) { $description = '

      ' . t('These settings apply to the %field field everywhere it is used. Because the field already has data, some settings can no longer be changed.', array('%field' => $instance['label'])) . '

      '; } else { $description = '

      ' . t('These settings apply to the %field field everywhere it is used.', array('%field' => $instance['label'])) . '

      '; } // Create a form structure for the field values. $form['field'] = array( '#type' => 'fieldset', '#title' => t('%field field settings', array('%field' => $instance['label'])), '#description' => $description, '#tree' => TRUE, ); // Build the configurable field values. $description = t('Maximum number of values users can enter for this field.'); if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { $description .= '
      ' . t("'Unlimited' will provide an 'Add more' button so the users can add as many values as they like."); } $form['field']['cardinality'] = array( '#type' => 'select', '#title' => t('Number of values'), '#options' => array(FIELD_CARDINALITY_UNLIMITED => t('Unlimited')) + drupal_map_assoc(range(1, 10)), '#default_value' => $field['cardinality'], '#description' => $description, ); // Add additional field type settings. The field type module is // responsible for not returning settings that cannot be changed if // the field already has data. $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance, $has_data); if (is_array($additions)) { $form['field']['settings'] = $additions; } $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save settings')); return $form; } /** * Pre-render function for field instance settings. * * Combines the instance, widget, and other settings into a single fieldset so * that elements within each group can be shown at different weights as if they * all had the same parent. */ function field_ui_field_edit_instance_pre_render($element) { // Merge the widget settings into the main form. if (isset($element['widget']['settings'])) { foreach (element_children($element['widget']['settings']) as $key) { $element['widget_' . $key] = $element['widget']['settings'][$key]; } unset($element['widget']['settings']); } // Merge the instance settings into the main form. if (isset($element['settings'])) { foreach (element_children($element['settings']) as $key) { $element['instance_' . $key] = $element['settings'][$key]; } unset($element['settings']); } return $element; } /** * Builds the default value fieldset for a given field instance. */ function field_ui_default_value_widget($field, $instance, &$form, &$form_state) { $field_name = $field['field_name']; $element = array( '#type' => 'fieldset', '#title' => t('Default value'), '#collapsible' => FALSE, '#tree' => TRUE, '#description' => t('The default value for this field, used when creating new content.'), // Stick to an empty 'parents' on this form in order not to breaks widgets // that do not use field_widget_[field|instance]() and still access // $form_state['field'] directly. '#parents' => array(), ); // Insert the widget. $items = $instance['default_value']; $instance['required'] = FALSE; $instance['description'] = ''; // @todo Allow multiple values (requires more work on 'add more' JS handler). $element += field_default_form($instance['entity_type'], NULL, $field, $instance, LANGUAGE_NONE, $items, $element, $form_state, 0); return $element; } /** * Form validation handler for field_ui_field_edit_form(). * * @see field_ui_field_edit_form_submit(). */ function field_ui_field_edit_form_validate($form, &$form_state) { // Take the incoming values as the $instance definition, so that the 'default // value' gets validated using the instance settings being submitted. $instance = $form_state['values']['instance']; $field_name = $instance['field_name']; if (isset($form['instance']['default_value_widget'])) { $element = $form['instance']['default_value_widget']; $field_state = field_form_get_state($element['#parents'], $field_name, LANGUAGE_NONE, $form_state); $field = $field_state['field']; // Extract the 'default value'. $items = array(); field_default_extract_form_values(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $element, $form_state); // Validate the value and report errors. $errors = array(); $function = $field['module'] . '_field_validate'; if (function_exists($function)) { $function(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $errors); } if (isset($errors[$field_name][LANGUAGE_NONE])) { // Store reported errors in $form_state. $field_state['errors'] = $errors[$field_name][LANGUAGE_NONE]; field_form_set_state($element['#parents'], $field_name, LANGUAGE_NONE, $form_state, $field_state); // Assign reported errors to the correct form element. field_default_form_errors(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $element, $form_state); } } } /** * Form submission handler for field_ui_field_edit_form(). * * @see field_ui_field_edit_form_validate(). */ function field_ui_field_edit_form_submit($form, &$form_state) { $instance = $form_state['values']['instance']; $field = $form_state['values']['field']; // Update any field settings that have changed. $field_source = field_info_field($instance['field_name']); $field = array_merge($field_source, $field); try { field_update_field($field); } catch (Exception $e) { drupal_set_message(t('Attempt to update field %label failed: %message.', array('%label' => $instance['label'], '%message' => $e->getMessage())), 'error'); return; } // Handle the default value. if (isset($form['instance']['default_value_widget'])) { $element = $form['instance']['default_value_widget']; // Extract field values. $items = array(); field_default_extract_form_values(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $element, $form_state); field_default_submit(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $element, $form_state); $instance['default_value'] = $items ? $items : NULL; } // Retrieve the stored instance settings to merge with the incoming values. $instance_source = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); $instance = array_merge($instance_source, $instance); field_update_instance($instance); drupal_set_message(t('Saved %label configuration.', array('%label' => $instance['label']))); $form_state['redirect'] = field_ui_next_destination($instance['entity_type'], $instance['bundle']); } /** * Extracts next redirect path from an array of multiple destinations. * * @see field_ui_next_destination() */ function field_ui_get_destinations($destinations) { $path = array_shift($destinations); $options = drupal_parse_url($path); if ($destinations) { $options['query']['destinations'] = $destinations; } return array($options['path'], $options); } /** * Returns the next redirect path in a multipage sequence. */ function field_ui_next_destination($entity_type, $bundle) { $destinations = !empty($_REQUEST['destinations']) ? $_REQUEST['destinations'] : array(); if (!empty($destinations)) { unset($_REQUEST['destinations']); return field_ui_get_destinations($destinations); } $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); return $admin_path . '/fields'; } drupal-7.26/modules/field_ui/field_ui.api.php0000644001412200141220000001356012265562324020516 0ustar benderbender 'textfield', '#title' => t('Maximum length'), '#default_value' => $settings['max_length'], '#required' => FALSE, '#element_validate' => array('element_validate_integer_positive'), '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'), ); return $form; } /** * Add settings to an instance field settings form. * * Invoked from field_ui_field_edit_form() to allow the module defining the * field to add settings for a field instance. * * @param $field * The field structure being configured. * @param $instance * The instance structure being configured. * * @return * The form definition for the field instance settings. */ function hook_field_instance_settings_form($field, $instance) { $settings = $instance['settings']; $form['text_processing'] = array( '#type' => 'radios', '#title' => t('Text processing'), '#default_value' => $settings['text_processing'], '#options' => array( t('Plain text'), t('Filtered text (user selects text format)'), ), ); if ($field['type'] == 'text_with_summary') { $form['display_summary'] = array( '#type' => 'select', '#title' => t('Display summary'), '#options' => array( t('No'), t('Yes'), ), '#description' => t('Display the summary to allow the user to input a summary value. Hide the summary to automatically fill it with a trimmed portion from the main post.'), '#default_value' => !empty($settings['display_summary']) ? $settings['display_summary'] : 0, ); } return $form; } /** * Add settings to a widget settings form. * * Invoked from field_ui_field_edit_form() to allow the module defining the * widget to add settings for a widget instance. * * @param $field * The field structure being configured. * @param $instance * The instance structure being configured. * * @return * The form definition for the widget settings. */ function hook_field_widget_settings_form($field, $instance) { $widget = $instance['widget']; $settings = $widget['settings']; if ($widget['type'] == 'text_textfield') { $form['size'] = array( '#type' => 'textfield', '#title' => t('Size of textfield'), '#default_value' => $settings['size'], '#element_validate' => array('element_validate_integer_positive'), '#required' => TRUE, ); } else { $form['rows'] = array( '#type' => 'textfield', '#title' => t('Rows'), '#default_value' => $settings['rows'], '#element_validate' => array('element_validate_integer_positive'), '#required' => TRUE, ); } return $form; } /** * Specify the form elements for a formatter's settings. * * @param $field * The field structure being configured. * @param $instance * The instance structure being configured. * @param $view_mode * The view mode being configured. * @param $form * The (entire) configuration form array, which will usually have no use here. * @param $form_state * The form state of the (entire) configuration form. * * @return * The form elements for the formatter settings. */ function hook_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { $display = $instance['display'][$view_mode]; $settings = $display['settings']; $element = array(); if ($display['type'] == 'text_trimmed' || $display['type'] == 'text_summary_or_trimmed') { $element['trim_length'] = array( '#title' => t('Length'), '#type' => 'textfield', '#size' => 20, '#default_value' => $settings['trim_length'], '#element_validate' => array('element_validate_integer_positive'), '#required' => TRUE, ); } return $element; } /** * Return a short summary for the current formatter settings of an instance. * * If an empty result is returned, the formatter is assumed to have no * configurable settings, and no UI will be provided to display a settings * form. * * @param $field * The field structure. * @param $instance * The instance structure. * @param $view_mode * The view mode for which a settings summary is requested. * * @return * A string containing a short summary of the formatter settings. */ function hook_field_formatter_settings_summary($field, $instance, $view_mode) { $display = $instance['display'][$view_mode]; $settings = $display['settings']; $summary = ''; if ($display['type'] == 'text_trimmed' || $display['type'] == 'text_summary_or_trimmed') { $summary = t('Length: @chars chars', array('@chars' => $settings['trim_length'])); } return $summary; } /** * @} End of "addtogroup field_types". */ drupal-7.26/modules/README.txt0000644001412200141220000000070012265562324015363 0ustar benderbender This directory is reserved for core module files. Custom or contributed modules should be placed in their own subdirectory of the sites/all/modules directory. For multisite installations, they can also be placed in a subdirectory under /sites/{sitename}/modules/, where {sitename} is the name of your site (e.g., www.example.com). This will allow you to more easily update Drupal core files. For more details, see: http://drupal.org/node/176043 drupal-7.26/modules/simpletest/0000755001412200141220000000000012265562324016061 5ustar benderbenderdrupal-7.26/modules/simpletest/simpletest.info0000644001412200141220000000365312265564172021141 0ustar benderbendername = Testing description = Provides a framework for unit and functional testing. package = Core version = VERSION core = 7.x files[] = simpletest.test files[] = drupal_web_test_case.php configure = admin/config/development/testing/settings ; Tests in tests directory. files[] = tests/actions.test files[] = tests/ajax.test files[] = tests/batch.test files[] = tests/bootstrap.test files[] = tests/cache.test files[] = tests/common.test files[] = tests/database_test.test files[] = tests/entity_crud_hook_test.test files[] = tests/entity_query.test files[] = tests/error.test files[] = tests/file.test files[] = tests/filetransfer.test files[] = tests/form.test files[] = tests/graph.test files[] = tests/image.test files[] = tests/lock.test files[] = tests/mail.test files[] = tests/menu.test files[] = tests/module.test files[] = tests/pager.test files[] = tests/password.test files[] = tests/path.test files[] = tests/registry.test files[] = tests/schema.test files[] = tests/session.test files[] = tests/tablesort.test files[] = tests/theme.test files[] = tests/unicode.test files[] = tests/update.test files[] = tests/xmlrpc.test files[] = tests/upgrade/upgrade.test files[] = tests/upgrade/upgrade.comment.test files[] = tests/upgrade/upgrade.filter.test files[] = tests/upgrade/upgrade.forum.test files[] = tests/upgrade/upgrade.locale.test files[] = tests/upgrade/upgrade.menu.test files[] = tests/upgrade/upgrade.node.test files[] = tests/upgrade/upgrade.taxonomy.test files[] = tests/upgrade/upgrade.trigger.test files[] = tests/upgrade/upgrade.translatable.test files[] = tests/upgrade/upgrade.upload.test files[] = tests/upgrade/upgrade.user.test files[] = tests/upgrade/update.aggregator.test files[] = tests/upgrade/update.trigger.test files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test ; Information added by Drupal.org packaging script on 2014-01-15 version = "7.26" project = "drupal" datestamp = "1389815930" drupal-7.26/modules/simpletest/simpletest.pages.inc0000644001412200141220000004313612265562324022052 0ustar benderbender 'fieldset', '#title' => t('Tests'), '#description' => t('Select the test(s) or test group(s) you would like to run, and click Run tests.'), ); $form['tests']['table'] = array( '#theme' => 'simpletest_test_table', ); // Generate the list of tests arranged by group. $groups = simpletest_test_get_all(); foreach ($groups as $group => $tests) { $form['tests']['table'][$group] = array( '#collapsed' => TRUE, ); foreach ($tests as $class => $info) { $form['tests']['table'][$group][$class] = array( '#type' => 'checkbox', '#title' => $info['name'], '#description' => $info['description'], ); } } // Operation buttons. $form['tests']['op'] = array( '#type' => 'submit', '#value' => t('Run tests'), ); $form['clean'] = array( '#type' => 'fieldset', '#collapsible' => FALSE, '#collapsed' => FALSE, '#title' => t('Clean test environment'), '#description' => t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed. This is intended for developers when creating tests.'), ); $form['clean']['op'] = array( '#type' => 'submit', '#value' => t('Clean environment'), '#submit' => array('simpletest_clean_environment'), ); return $form; } /** * Returns HTML for a test list generated by simpletest_test_form() into a table. * * @param $variables * An associative array containing: * - table: A render element representing the table. * * @ingroup themeable */ function theme_simpletest_test_table($variables) { $table = $variables['table']; drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css'); drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js'); drupal_add_js('misc/tableselect.js'); // Create header for test selection table. $header = array( array('class' => array('select-all')), array('data' => t('Test'), 'class' => array('simpletest_test')), array('data' => t('Description'), 'class' => array('simpletest_description')), ); // Define the images used to expand/collapse the test groups. $js = array( 'images' => array( theme('image', array('path' => 'misc/menu-collapsed.png', 'width' => 7, 'height' => 7, 'alt' => t('Expand'), 'title' => t('Expand'))) . ' (' . t('Expand') . ')', theme('image', array('path' => 'misc/menu-expanded.png', 'width' => 7, 'height' => 7, 'alt' => t('Collapse'), 'title' => t('Collapse'))) . ' (' . t('Collapse') . ')', ), ); // Cycle through each test group and create a row. $rows = array(); foreach (element_children($table) as $key) { $element = &$table[$key]; $row = array(); // Make the class name safe for output on the page by replacing all // non-word/decimal characters with a dash (-). $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key))); // Select the right "expand"/"collapse" image, depending on whether the // category is expanded (at least one test selected) or not. $collapsed = !empty($element['#collapsed']); $image_index = $collapsed ? 0 : 1; // Place-holder for checkboxes to select group of tests. $row[] = array('id' => $test_class, 'class' => array('simpletest-select-all')); // Expand/collapse image and group title. $row[] = array( 'data' => '
      ' . '', 'class' => array('simpletest-group-label'), ); $row[] = array( 'data' => ' ', 'class' => array('simpletest-group-description'), ); $rows[] = array('data' => $row, 'class' => array('simpletest-group')); // Add individual tests to group. $current_js = array( 'testClass' => $test_class . '-test', 'testNames' => array(), 'imageDirection' => $image_index, 'clickActive' => FALSE, ); // Sorting $element by children's #title attribute instead of by class name. uasort($element, 'element_sort_by_title'); // Cycle through each test within the current group. foreach (element_children($element) as $test_name) { $test = $element[$test_name]; $row = array(); $current_js['testNames'][] = $test['#id']; // Store test title and description so that checkbox won't render them. $title = $test['#title']; $description = $test['#description']; $test['#title_display'] = 'invisible'; unset($test['#description']); // Test name is used to determine what tests to run. $test['#name'] = $test_name; $row[] = array( 'data' => drupal_render($test), 'class' => array('simpletest-test-select'), ); $row[] = array( 'data' => '', 'class' => array('simpletest-test-label'), ); $row[] = array( 'data' => '
      ' . $description . '
      ', 'class' => array('simpletest-test-description'), ); $rows[] = array('data' => $row, 'class' => array($test_class . '-test', ($collapsed ? 'js-hide' : ''))); } $js['simpletest-test-group-' . $test_class] = $current_js; unset($table[$key]); } // Add js array of settings. drupal_add_js(array('simpleTest' => $js), 'setting'); if (empty($rows)) { return '' . t('No tests to display.') . ''; } else { return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'simpletest-form-table'))); } } /** * Run selected tests. */ function simpletest_test_form_submit($form, &$form_state) { simpletest_classloader_register(); // Get list of tests. $tests_list = array(); foreach ($form_state['values'] as $class_name => $value) { // Since class_exists() will likely trigger an autoload lookup, // we do the fast check first. if ($value === 1 && class_exists($class_name)) { $tests_list[] = $class_name; } } if (count($tests_list) > 0 ) { $test_id = simpletest_run_tests($tests_list, 'drupal'); $form_state['redirect'] = 'admin/config/development/testing/results/' . $test_id; } else { drupal_set_message(t('No test(s) selected.'), 'error'); } } /** * Test results form for $test_id. */ function simpletest_result_form($form, &$form_state, $test_id) { // Make sure there are test results to display and a re-run is not being performed. $results = array(); if (is_numeric($test_id) && !$results = simpletest_result_get($test_id)) { drupal_set_message(t('No test results to display.'), 'error'); drupal_goto('admin/config/development/testing'); return $form; } // Load all classes and include CSS. drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css'); // Keep track of which test cases passed or failed. $filter = array( 'pass' => array(), 'fail' => array(), ); // Summary result fieldset. $form['result'] = array( '#type' => 'fieldset', '#title' => t('Results'), ); $form['result']['summary'] = $summary = array( '#theme' => 'simpletest_result_summary', '#pass' => 0, '#fail' => 0, '#exception' => 0, '#debug' => 0, ); simpletest_classloader_register(); // Cycle through each test group. $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status'))); $form['result']['results'] = array(); foreach ($results as $group => $assertions) { // Create group fieldset with summary information. $info = call_user_func(array($group, 'getInfo')); $form['result']['results'][$group] = array( '#type' => 'fieldset', '#title' => $info['name'], '#description' => $info['description'], '#collapsible' => TRUE, ); $form['result']['results'][$group]['summary'] = $summary; $group_summary = &$form['result']['results'][$group]['summary']; // Create table of assertions for the group. $rows = array(); foreach ($assertions as $assertion) { $row = array(); $row[] = $assertion->message; $row[] = $assertion->message_group; $row[] = drupal_basename($assertion->file); $row[] = $assertion->line; $row[] = $assertion->function; $row[] = simpletest_result_status_image($assertion->status); $class = 'simpletest-' . $assertion->status; if ($assertion->message_group == 'Debug') { $class = 'simpletest-debug'; } $rows[] = array('data' => $row, 'class' => array($class)); $group_summary['#' . $assertion->status]++; $form['result']['summary']['#' . $assertion->status]++; } $form['result']['results'][$group]['table'] = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, ); // Set summary information. $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0; $form['result']['results'][$group]['#collapsed'] = $group_summary['#ok']; // Store test group (class) as for use in filter. $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group; } // Overal summary status. $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0; // Actions. $form['#action'] = url('admin/config/development/testing/results/re-run'); $form['action'] = array( '#type' => 'fieldset', '#title' => t('Actions'), '#attributes' => array('class' => array('container-inline')), '#weight' => -11, ); $form['action']['filter'] = array( '#type' => 'select', '#title' => 'Filter', '#options' => array( 'all' => t('All (@count)', array('@count' => count($filter['pass']) + count($filter['fail']))), 'pass' => t('Pass (@count)', array('@count' => count($filter['pass']))), 'fail' => t('Fail (@count)', array('@count' => count($filter['fail']))), ), ); $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all'); // Categorized test classes for to be used with selected filter value. $form['action']['filter_pass'] = array( '#type' => 'hidden', '#default_value' => implode(',', $filter['pass']), ); $form['action']['filter_fail'] = array( '#type' => 'hidden', '#default_value' => implode(',', $filter['fail']), ); $form['action']['op'] = array( '#type' => 'submit', '#value' => t('Run tests'), ); $form['action']['return'] = array( '#type' => 'link', '#title' => t('Return to list'), '#href' => 'admin/config/development/testing', ); if (is_numeric($test_id)) { simpletest_clean_results_table($test_id); } return $form; } /** * Re-run the tests that match the filter. */ function simpletest_result_form_submit($form, &$form_state) { $pass = $form_state['values']['filter_pass'] ? explode(',', $form_state['values']['filter_pass']) : array(); $fail = $form_state['values']['filter_fail'] ? explode(',', $form_state['values']['filter_fail']) : array(); if ($form_state['values']['filter'] == 'all') { $classes = array_merge($pass, $fail); } elseif ($form_state['values']['filter'] == 'pass') { $classes = $pass; } else { $classes = $fail; } if (!$classes) { $form_state['redirect'] = 'admin/config/development/testing'; return; } $form_state_execute = array('values' => array()); foreach ($classes as $class) { $form_state_execute['values'][$class] = 1; } simpletest_test_form_submit(array(), $form_state_execute); $form_state['redirect'] = $form_state_execute['redirect']; } /** * Returns HTML for the summary status of a simpletest result. * * @param $variables * An associative array containing: * - form: A render element representing the form. * * @ingroup themeable */ function theme_simpletest_result_summary($variables) { $form = $variables['form']; return '
      ' . _simpletest_format_summary_line($form) . '
      '; } /** * Get test results for $test_id. * * @param $test_id The test_id to retrieve results of. * @return Array of results grouped by test_class. */ function simpletest_result_get($test_id) { $results = db_select('simpletest') ->fields('simpletest') ->condition('test_id', $test_id) ->orderBy('test_class') ->orderBy('message_id') ->execute(); $test_results = array(); foreach ($results as $result) { if (!isset($test_results[$result->test_class])) { $test_results[$result->test_class] = array(); } $test_results[$result->test_class][] = $result; } return $test_results; } /** * Get the appropriate image for the status. * * @param $status Status string, either: pass, fail, exception. * @return HTML image or false. */ function simpletest_result_status_image($status) { // $map does not use drupal_static() as its value never changes. static $map; if (!isset($map)) { $map = array( 'pass' => theme('image', array('path' => 'misc/watchdog-ok.png', 'width' => 18, 'height' => 18, 'alt' => t('Pass'))), 'fail' => theme('image', array('path' => 'misc/watchdog-error.png', 'width' => 18, 'height' => 18, 'alt' => t('Fail'))), 'exception' => theme('image', array('path' => 'misc/watchdog-warning.png', 'width' => 18, 'height' => 18, 'alt' => t('Exception'))), 'debug' => theme('image', array('path' => 'misc/watchdog-warning.png', 'width' => 18, 'height' => 18, 'alt' => t('Debug'))), ); } if (isset($map[$status])) { return $map[$status]; } return FALSE; } /** * Provides settings form for SimpleTest variables. * * @ingroup forms * @see simpletest_settings_form_validate() */ function simpletest_settings_form($form, &$form_state) { $form['general'] = array( '#type' => 'fieldset', '#title' => t('General'), ); $form['general']['simpletest_clear_results'] = array( '#type' => 'checkbox', '#title' => t('Clear results after each complete test suite run'), '#description' => t('By default SimpleTest will clear the results after they have been viewed on the results page, but in some cases it may be useful to leave the results in the database. The results can then be viewed at admin/config/development/testing/[test_id]. The test ID can be found in the database, simpletest table, or kept track of when viewing the results the first time. Additionally, some modules may provide more analysis or features that require this setting to be disabled.'), '#default_value' => variable_get('simpletest_clear_results', TRUE), ); $form['general']['simpletest_verbose'] = array( '#type' => 'checkbox', '#title' => t('Provide verbose information when running tests'), '#description' => t('The verbose data will be printed along with the standard assertions and is useful for debugging. The verbose data will be erased between each test suite run. The verbose data output is very detailed and should only be used when debugging.'), '#default_value' => variable_get('simpletest_verbose', TRUE), ); $form['httpauth'] = array( '#type' => 'fieldset', '#title' => t('HTTP authentication'), '#description' => t('HTTP auth settings to be used by the SimpleTest browser during testing. Useful when the site requires basic HTTP authentication.'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['httpauth']['simpletest_httpauth_method'] = array( '#type' => 'select', '#title' => t('Method'), '#options' => array( CURLAUTH_BASIC => t('Basic'), CURLAUTH_DIGEST => t('Digest'), CURLAUTH_GSSNEGOTIATE => t('GSS negotiate'), CURLAUTH_NTLM => t('NTLM'), CURLAUTH_ANY => t('Any'), CURLAUTH_ANYSAFE => t('Any safe'), ), '#default_value' => variable_get('simpletest_httpauth_method', CURLAUTH_BASIC), ); $username = variable_get('simpletest_httpauth_username'); $password = variable_get('simpletest_httpauth_password'); $form['httpauth']['simpletest_httpauth_username'] = array( '#type' => 'textfield', '#title' => t('Username'), '#default_value' => $username, ); if ($username && $password) { $form['httpauth']['simpletest_httpauth_username']['#description'] = t('Leave this blank to delete both the existing username and password.'); } $form['httpauth']['simpletest_httpauth_password'] = array( '#type' => 'password', '#title' => t('Password'), ); if ($password) { $form['httpauth']['simpletest_httpauth_password']['#description'] = t('To change the password, enter the new password here.'); } return system_settings_form($form); } /** * Validation handler for simpletest_settings_form(). */ function simpletest_settings_form_validate($form, &$form_state) { // If a username was provided but a password wasn't, preserve the existing // password. if (!empty($form_state['values']['simpletest_httpauth_username']) && empty($form_state['values']['simpletest_httpauth_password'])) { $form_state['values']['simpletest_httpauth_password'] = variable_get('simpletest_httpauth_password', ''); } // If a password was provided but a username wasn't, the credentials are // incorrect, so throw an error. if (empty($form_state['values']['simpletest_httpauth_username']) && !empty($form_state['values']['simpletest_httpauth_password'])) { form_set_error('simpletest_httpauth_username', t('HTTP authentication credentials must include a username in addition to a password.')); } } drupal-7.26/modules/simpletest/simpletest.js0000644001412200141220000000701212265562324020610 0ustar benderbender(function ($) { /** * Add the cool table collapsing on the testing overview page. */ Drupal.behaviors.simpleTestMenuCollapse = { attach: function (context, settings) { var timeout = null; // Adds expand-collapse functionality. $('div.simpletest-image').once('simpletest-image', function () { var $this = $(this); var direction = settings.simpleTest[this.id].imageDirection; $this.html(settings.simpleTest.images[direction]); // Adds group toggling functionality to arrow images. $this.click(function () { var trs = $this.closest('tbody').children('.' + settings.simpleTest[this.id].testClass); var direction = settings.simpleTest[this.id].imageDirection; var row = direction ? trs.length - 1 : 0; // If clicked in the middle of expanding a group, stop so we can switch directions. if (timeout) { clearTimeout(timeout); } // Function to toggle an individual row according to the current direction. // We set a timeout of 20 ms until the next row will be shown/hidden to // create a sliding effect. function rowToggle() { if (direction) { if (row >= 0) { $(trs[row]).hide(); row--; timeout = setTimeout(rowToggle, 20); } } else { if (row < trs.length) { $(trs[row]).removeClass('js-hide').show(); row++; timeout = setTimeout(rowToggle, 20); } } } // Kick-off the toggling upon a new click. rowToggle(); // Toggle the arrow image next to the test group title. $this.html(settings.simpleTest.images[(direction ? 0 : 1)]); settings.simpleTest[this.id].imageDirection = !direction; }); }); } }; /** * Select/deselect all the inner checkboxes when the outer checkboxes are * selected/deselected. */ Drupal.behaviors.simpleTestSelectAll = { attach: function (context, settings) { $('td.simpletest-select-all').once('simpletest-select-all', function () { var testCheckboxes = settings.simpleTest['simpletest-test-group-' + $(this).attr('id')].testNames; var groupCheckbox = $(''); // Each time a single-test checkbox is checked or unchecked, make sure // that the associated group checkbox gets the right state too. var updateGroupCheckbox = function () { var checkedTests = 0; for (var i = 0; i < testCheckboxes.length; i++) { $('#' + testCheckboxes[i]).each(function () { if (($(this).attr('checked'))) { checkedTests++; } }); } $(groupCheckbox).attr('checked', (checkedTests == testCheckboxes.length)); }; // Have the single-test checkboxes follow the group checkbox. groupCheckbox.change(function () { var checked = !!($(this).attr('checked')); for (var i = 0; i < testCheckboxes.length; i++) { $('#' + testCheckboxes[i]).attr('checked', checked); } }); // Have the group checkbox follow the single-test checkboxes. for (var i = 0; i < testCheckboxes.length; i++) { $('#' + testCheckboxes[i]).change(function () { updateGroupCheckbox(); }); } // Initialize status for the group checkbox correctly. updateGroupCheckbox(); $(this).append(groupCheckbox); }); } }; })(jQuery); drupal-7.26/modules/simpletest/drupal_web_test_case.php0000644001412200141220000040106412265562324022755 0ustar benderbender 0, '#fail' => 0, '#exception' => 0, '#debug' => 0, ); /** * Assertions thrown in that test case. * * @var Array */ protected $assertions = array(); /** * This class is skipped when looking for the source of an assertion. * * When displaying which function an assert comes from, it's not too useful * to see "drupalWebTestCase->drupalLogin()', we would like to see the test * that called it. So we need to skip the classes defining these helper * methods. */ protected $skipClasses = array(__CLASS__ => TRUE); /** * Flag to indicate whether the test has been set up. * * The setUp() method isolates the test from the parent Drupal site by * creating a random prefix for the database and setting up a clean file * storage directory. The tearDown() method then cleans up this test * environment. We must ensure that setUp() has been run. Otherwise, * tearDown() will act on the parent Drupal site rather than the test * environment, destroying live data. */ protected $setup = FALSE; protected $setupDatabasePrefix = FALSE; protected $setupEnvironment = FALSE; /** * Constructor for DrupalTestCase. * * @param $test_id * Tests with the same id are reported together. */ public function __construct($test_id = NULL) { $this->testId = $test_id; } /** * Internal helper: stores the assert. * * @param $status * Can be 'pass', 'fail', 'exception'. * TRUE is a synonym for 'pass', FALSE for 'fail'. * @param $message * The message string. * @param $group * Which group this assert belongs to. * @param $caller * By default, the assert comes from a function whose name starts with * 'test'. Instead, you can specify where this assert originates from * by passing in an associative array as $caller. Key 'file' is * the name of the source file, 'line' is the line number and 'function' * is the caller function itself. */ protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) { // Convert boolean status to string status. if (is_bool($status)) { $status = $status ? 'pass' : 'fail'; } // Increment summary result counter. $this->results['#' . $status]++; // Get the function information about the call to the assertion method. if (!$caller) { $caller = $this->getAssertionCall(); } // Creation assertion array that can be displayed while tests are running. $this->assertions[] = $assertion = array( 'test_id' => $this->testId, 'test_class' => get_class($this), 'status' => $status, 'message' => $message, 'message_group' => $group, 'function' => $caller['function'], 'line' => $caller['line'], 'file' => $caller['file'], ); // Store assertion for display after the test has completed. try { $connection = Database::getConnection('default', 'simpletest_original_default'); } catch (DatabaseConnectionNotDefinedException $e) { // If the test was not set up, the simpletest_original_default // connection does not exist. $connection = Database::getConnection('default', 'default'); } $connection ->insert('simpletest') ->fields($assertion) ->execute(); // We do not use a ternary operator here to allow a breakpoint on // test failure. if ($status == 'pass') { return TRUE; } else { return FALSE; } } /** * Store an assertion from outside the testing context. * * This is useful for inserting assertions that can only be recorded after * the test case has been destroyed, such as PHP fatal errors. The caller * information is not automatically gathered since the caller is most likely * inserting the assertion on behalf of other code. In all other respects * the method behaves just like DrupalTestCase::assert() in terms of storing * the assertion. * * @return * Message ID of the stored assertion. * * @see DrupalTestCase::assert() * @see DrupalTestCase::deleteAssert() */ public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = array()) { // Convert boolean status to string status. if (is_bool($status)) { $status = $status ? 'pass' : 'fail'; } $caller += array( 'function' => t('Unknown'), 'line' => 0, 'file' => t('Unknown'), ); $assertion = array( 'test_id' => $test_id, 'test_class' => $test_class, 'status' => $status, 'message' => $message, 'message_group' => $group, 'function' => $caller['function'], 'line' => $caller['line'], 'file' => $caller['file'], ); return db_insert('simpletest') ->fields($assertion) ->execute(); } /** * Delete an assertion record by message ID. * * @param $message_id * Message ID of the assertion to delete. * @return * TRUE if the assertion was deleted, FALSE otherwise. * * @see DrupalTestCase::insertAssert() */ public static function deleteAssert($message_id) { return (bool) db_delete('simpletest') ->condition('message_id', $message_id) ->execute(); } /** * Cycles through backtrace until the first non-assertion method is found. * * @return * Array representing the true caller. */ protected function getAssertionCall() { $backtrace = debug_backtrace(); // The first element is the call. The second element is the caller. // We skip calls that occurred in one of the methods of our base classes // or in an assertion function. while (($caller = $backtrace[1]) && ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) || substr($caller['function'], 0, 6) == 'assert')) { // We remove that call. array_shift($backtrace); } return _drupal_get_last_caller($backtrace); } /** * Check to see if a value is not false (not an empty string, 0, NULL, or FALSE). * * @param $value * The value on which the assertion is to be done. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertTrue($value, $message = '', $group = 'Other') { return $this->assert((bool) $value, $message ? $message : t('Value @value is TRUE.', array('@value' => var_export($value, TRUE))), $group); } /** * Check to see if a value is false (an empty string, 0, NULL, or FALSE). * * @param $value * The value on which the assertion is to be done. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertFalse($value, $message = '', $group = 'Other') { return $this->assert(!$value, $message ? $message : t('Value @value is FALSE.', array('@value' => var_export($value, TRUE))), $group); } /** * Check to see if a value is NULL. * * @param $value * The value on which the assertion is to be done. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNull($value, $message = '', $group = 'Other') { return $this->assert(!isset($value), $message ? $message : t('Value @value is NULL.', array('@value' => var_export($value, TRUE))), $group); } /** * Check to see if a value is not NULL. * * @param $value * The value on which the assertion is to be done. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNotNull($value, $message = '', $group = 'Other') { return $this->assert(isset($value), $message ? $message : t('Value @value is not NULL.', array('@value' => var_export($value, TRUE))), $group); } /** * Check to see if two values are equal. * * @param $first * The first value to check. * @param $second * The second value to check. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertEqual($first, $second, $message = '', $group = 'Other') { return $this->assert($first == $second, $message ? $message : t('Value @first is equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group); } /** * Check to see if two values are not equal. * * @param $first * The first value to check. * @param $second * The second value to check. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNotEqual($first, $second, $message = '', $group = 'Other') { return $this->assert($first != $second, $message ? $message : t('Value @first is not equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group); } /** * Check to see if two values are identical. * * @param $first * The first value to check. * @param $second * The second value to check. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertIdentical($first, $second, $message = '', $group = 'Other') { return $this->assert($first === $second, $message ? $message : t('Value @first is identical to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group); } /** * Check to see if two values are not identical. * * @param $first * The first value to check. * @param $second * The second value to check. * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') { return $this->assert($first !== $second, $message ? $message : t('Value @first is not identical to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group); } /** * Fire an assertion that is always positive. * * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * TRUE. */ protected function pass($message = NULL, $group = 'Other') { return $this->assert(TRUE, $message, $group); } /** * Fire an assertion that is always negative. * * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @return * FALSE. */ protected function fail($message = NULL, $group = 'Other') { return $this->assert(FALSE, $message, $group); } /** * Fire an error assertion. * * @param $message * The message to display along with the assertion. * @param $group * The type of assertion - examples are "Browser", "PHP". * @param $caller * The caller of the error. * @return * FALSE. */ protected function error($message = '', $group = 'Other', array $caller = NULL) { if ($group == 'User notice') { // Since 'User notice' is set by trigger_error() which is used for debug // set the message to a status of 'debug'. return $this->assert('debug', $message, 'Debug', $caller); } return $this->assert('exception', $message, $group, $caller); } /** * Logs verbose message in a text file. * * The a link to the vebose message will be placed in the test results via * as a passing assertion with the text '[verbose message]'. * * @param $message * The verbose message to be stored. * * @see simpletest_verbose() */ protected function verbose($message) { if ($id = simpletest_verbose($message)) { $class_safe = str_replace('\\', '_', get_class($this)); $url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html'); $this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice'); } } /** * Run all tests in this class. * * Regardless of whether $methods are passed or not, only method names * starting with "test" are executed. * * @param $methods * (optional) A list of method names in the test case class to run; e.g., * array('testFoo', 'testBar'). By default, all methods of the class are * taken into account, but it can be useful to only run a few selected test * methods during debugging. */ public function run(array $methods = array()) { // Initialize verbose debugging. $class = get_class($this); simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), str_replace('\\', '_', $class)); // HTTP auth settings (:) for the simpletest browser // when sending requests to the test site. $this->httpauth_method = variable_get('simpletest_httpauth_method', CURLAUTH_BASIC); $username = variable_get('simpletest_httpauth_username', NULL); $password = variable_get('simpletest_httpauth_password', NULL); if ($username && $password) { $this->httpauth_credentials = $username . ':' . $password; } set_error_handler(array($this, 'errorHandler')); // Iterate through all the methods in this class, unless a specific list of // methods to run was passed. $class_methods = get_class_methods($class); if ($methods) { $class_methods = array_intersect($class_methods, $methods); } foreach ($class_methods as $method) { // If the current method starts with "test", run it - it's a test. if (strtolower(substr($method, 0, 4)) == 'test') { // Insert a fail record. This will be deleted on completion to ensure // that testing completed. $method_info = new ReflectionMethod($class, $method); $caller = array( 'file' => $method_info->getFileName(), 'line' => $method_info->getStartLine(), 'function' => $class . '->' . $method . '()', ); $completion_check_id = DrupalTestCase::insertAssert($this->testId, $class, FALSE, t('The test did not complete due to a fatal error.'), 'Completion check', $caller); $this->setUp(); if ($this->setup) { try { $this->$method(); // Finish up. } catch (Exception $e) { $this->exceptionHandler($e); } $this->tearDown(); } else { $this->fail(t("The test cannot be executed because it has not been set up properly.")); } // Remove the completion check record. DrupalTestCase::deleteAssert($completion_check_id); } } // Clear out the error messages and restore error handler. drupal_get_messages(); restore_error_handler(); } /** * Handle errors during test runs. * * Because this is registered in set_error_handler(), it has to be public. * @see set_error_handler */ public function errorHandler($severity, $message, $file = NULL, $line = NULL) { if ($severity & error_reporting()) { $error_map = array( E_STRICT => 'Run-time notice', E_WARNING => 'Warning', E_NOTICE => 'Notice', E_CORE_ERROR => 'Core error', E_CORE_WARNING => 'Core warning', E_USER_ERROR => 'User error', E_USER_WARNING => 'User warning', E_USER_NOTICE => 'User notice', E_RECOVERABLE_ERROR => 'Recoverable error', ); // PHP 5.3 adds new error logging constants. Add these conditionally for // backwards compatibility with PHP 5.2. if (defined('E_DEPRECATED')) { $error_map += array( E_DEPRECATED => 'Deprecated', E_USER_DEPRECATED => 'User deprecated', ); } $backtrace = debug_backtrace(); $this->error($message, $error_map[$severity], _drupal_get_last_caller($backtrace)); } return TRUE; } /** * Handle exceptions. * * @see set_exception_handler */ protected function exceptionHandler($exception) { $backtrace = $exception->getTrace(); // Push on top of the backtrace the call that generated the exception. array_unshift($backtrace, array( 'line' => $exception->getLine(), 'file' => $exception->getFile(), )); require_once DRUPAL_ROOT . '/includes/errors.inc'; // The exception message is run through check_plain() by _drupal_decode_exception(). $this->error(t('%type: !message in %function (line %line of %file).', _drupal_decode_exception($exception)), 'Uncaught exception', _drupal_get_last_caller($backtrace)); } /** * Generates a random string of ASCII characters of codes 32 to 126. * * The generated string includes alpha-numeric characters and common * miscellaneous characters. Use this method when testing general input * where the content is not restricted. * * Do not use this method when special characters are not possible (e.g., in * machine or file names that have already been validated); instead, * use DrupalWebTestCase::randomName(). * * @param $length * Length of random string to generate. * * @return * Randomly generated string. * * @see DrupalWebTestCase::randomName() */ public static function randomString($length = 8) { $str = ''; for ($i = 0; $i < $length; $i++) { $str .= chr(mt_rand(32, 126)); } return $str; } /** * Generates a random string containing letters and numbers. * * The string will always start with a letter. The letters may be upper or * lower case. This method is better for restricted inputs that do not * accept certain characters. For example, when testing input fields that * require machine readable values (i.e. without spaces and non-standard * characters) this method is best. * * Do not use this method when testing unvalidated user input. Instead, use * DrupalWebTestCase::randomString(). * * @param $length * Length of random string to generate. * * @return * Randomly generated string. * * @see DrupalWebTestCase::randomString() */ public static function randomName($length = 8) { $values = array_merge(range(65, 90), range(97, 122), range(48, 57)); $max = count($values) - 1; $str = chr(mt_rand(97, 122)); for ($i = 1; $i < $length; $i++) { $str .= chr($values[mt_rand(0, $max)]); } return $str; } /** * Converts a list of possible parameters into a stack of permutations. * * Takes a list of parameters containing possible values, and converts all of * them into a list of items containing every possible permutation. * * Example: * @code * $parameters = array( * 'one' => array(0, 1), * 'two' => array(2, 3), * ); * $permutations = DrupalTestCase::generatePermutations($parameters) * // Result: * $permutations == array( * array('one' => 0, 'two' => 2), * array('one' => 1, 'two' => 2), * array('one' => 0, 'two' => 3), * array('one' => 1, 'two' => 3), * ) * @endcode * * @param $parameters * An associative array of parameters, keyed by parameter name, and whose * values are arrays of parameter values. * * @return * A list of permutations, which is an array of arrays. Each inner array * contains the full list of parameters that have been passed, but with a * single value only. */ public static function generatePermutations($parameters) { $all_permutations = array(array()); foreach ($parameters as $parameter => $values) { $new_permutations = array(); // Iterate over all values of the parameter. foreach ($values as $value) { // Iterate over all existing permutations. foreach ($all_permutations as $permutation) { // Add the new parameter value to existing permutations. $new_permutations[] = $permutation + array($parameter => $value); } } // Replace the old permutations with the new permutations. $all_permutations = $new_permutations; } return $all_permutations; } } /** * Test case for Drupal unit tests. * * These tests can not access the database nor files. Calling any Drupal * function that needs the database will throw exceptions. These include * watchdog(), module_implements(), module_invoke_all() etc. */ class DrupalUnitTestCase extends DrupalTestCase { /** * Constructor for DrupalUnitTestCase. */ function __construct($test_id = NULL) { parent::__construct($test_id); $this->skipClasses[__CLASS__] = TRUE; } /** * Sets up unit test environment. * * Unlike DrupalWebTestCase::setUp(), DrupalUnitTestCase::setUp() does not * install modules because tests are performed without accessing the database. * Any required files must be explicitly included by the child class setUp() * method. */ protected function setUp() { global $conf; // Store necessary current values before switching to the test environment. $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); // Reset all statics so that test is performed with a clean environment. drupal_static_reset(); // Generate temporary prefixed database to ensure that tests have a clean starting point. $this->databasePrefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); // Create test directory. $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); file_prepare_directory($public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); $conf['file_public_path'] = $public_files_directory; // Clone the current connection and replace the current prefix. $connection_info = Database::getConnectionInfo('default'); Database::renameConnection('default', 'simpletest_original_default'); foreach ($connection_info as $target => $value) { $connection_info[$target]['prefix'] = array( 'default' => $value['prefix']['default'] . $this->databasePrefix, ); } Database::addConnectionInfo('default', 'default', $connection_info['default']); // Set user agent to be consistent with web test case. $_SERVER['HTTP_USER_AGENT'] = $this->databasePrefix; // If locale is enabled then t() will try to access the database and // subsequently will fail as the database is not accessible. $module_list = module_list(); if (isset($module_list['locale'])) { // Transform the list into the format expected as input to module_list(). foreach ($module_list as &$module) { $module = array('filename' => drupal_get_filename('module', $module)); } $this->originalModuleList = $module_list; unset($module_list['locale']); module_list(TRUE, FALSE, FALSE, $module_list); } $this->setup = TRUE; } protected function tearDown() { global $conf; // Get back to the original connection. Database::removeConnection('default'); Database::renameConnection('simpletest_original_default', 'default'); $conf['file_public_path'] = $this->originalFileDirectory; // Restore modules if necessary. if (isset($this->originalModuleList)) { module_list(TRUE, FALSE, FALSE, $this->originalModuleList); } } } /** * Test case for typical Drupal tests. */ class DrupalWebTestCase extends DrupalTestCase { /** * The profile to install as a basis for testing. * * @var string */ protected $profile = 'standard'; /** * The URL currently loaded in the internal browser. * * @var string */ protected $url; /** * The handle of the current cURL connection. * * @var resource */ protected $curlHandle; /** * The headers of the page currently loaded in the internal browser. * * @var Array */ protected $headers; /** * The content of the page currently loaded in the internal browser. * * @var string */ protected $content; /** * The content of the page currently loaded in the internal browser (plain text version). * * @var string */ protected $plainTextContent; /** * The value of the Drupal.settings JavaScript variable for the page currently loaded in the internal browser. * * @var Array */ protected $drupalSettings; /** * The parsed version of the page. * * @var SimpleXMLElement */ protected $elements = NULL; /** * The current user logged in using the internal browser. * * @var bool */ protected $loggedInUser = FALSE; /** * The current cookie file used by cURL. * * We do not reuse the cookies in further runs, so we do not need a file * but we still need cookie handling, so we set the jar to NULL. */ protected $cookieFile = NULL; /** * Additional cURL options. * * DrupalWebTestCase itself never sets this but always obeys what is set. */ protected $additionalCurlOptions = array(); /** * The original user, before it was changed to a clean uid = 1 for testing purposes. * * @var object */ protected $originalUser = NULL; /** * The original shutdown handlers array, before it was cleaned for testing purposes. * * @var array */ protected $originalShutdownCallbacks = array(); /** * HTTP authentication method */ protected $httpauth_method = CURLAUTH_BASIC; /** * HTTP authentication credentials (:). */ protected $httpauth_credentials = NULL; /** * The current session name, if available. */ protected $session_name = NULL; /** * The current session ID, if available. */ protected $session_id = NULL; /** * Whether the files were copied to the test files directory. */ protected $generatedTestFiles = FALSE; /** * The number of redirects followed during the handling of a request. */ protected $redirect_count; /** * Constructor for DrupalWebTestCase. */ function __construct($test_id = NULL) { parent::__construct($test_id); $this->skipClasses[__CLASS__] = TRUE; } /** * Get a node from the database based on its title. * * @param $title * A node title, usually generated by $this->randomName(). * @param $reset * (optional) Whether to reset the internal node_load() cache. * * @return * A node object matching $title. */ function drupalGetNodeByTitle($title, $reset = FALSE) { $nodes = node_load_multiple(array(), array('title' => $title), $reset); // Load the first node returned from the database. $returned_node = reset($nodes); return $returned_node; } /** * Creates a node based on default settings. * * @param $settings * An associative array of settings to change from the defaults, keys are * node properties, for example 'title' => 'Hello, world!'. * @return * Created node object. */ protected function drupalCreateNode($settings = array()) { // Populate defaults array. $settings += array( 'body' => array(LANGUAGE_NONE => array(array())), 'title' => $this->randomName(8), 'comment' => 2, 'changed' => REQUEST_TIME, 'moderate' => 0, 'promote' => 0, 'revision' => 1, 'log' => '', 'status' => 1, 'sticky' => 0, 'type' => 'page', 'revisions' => NULL, 'language' => LANGUAGE_NONE, ); // Use the original node's created time for existing nodes. if (isset($settings['created']) && !isset($settings['date'])) { $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); } // If the node's user uid is not specified manually, use the currently // logged in user if available, or else the user running the test. if (!isset($settings['uid'])) { if ($this->loggedInUser) { $settings['uid'] = $this->loggedInUser->uid; } else { global $user; $settings['uid'] = $user->uid; } } // Merge body field value and format separately. $body = array( 'value' => $this->randomName(32), 'format' => filter_default_format(), ); $settings['body'][$settings['language']][0] += $body; $node = (object) $settings; node_save($node); // Small hack to link revisions to our test user. db_update('node_revision') ->fields(array('uid' => $node->uid)) ->condition('vid', $node->vid) ->execute(); return $node; } /** * Creates a custom content type based on default settings. * * @param $settings * An array of settings to change from the defaults. * Example: 'type' => 'foo'. * @return * Created content type. */ protected function drupalCreateContentType($settings = array()) { // Find a non-existent random type name. do { $name = strtolower($this->randomName(8)); } while (node_type_get_type($name)); // Populate defaults array. $defaults = array( 'type' => $name, 'name' => $name, 'base' => 'node_content', 'description' => '', 'help' => '', 'title_label' => 'Title', 'body_label' => 'Body', 'has_title' => 1, 'has_body' => 1, ); // Imposed values for a custom type. $forced = array( 'orig_type' => '', 'old_type' => '', 'module' => 'node', 'custom' => 1, 'modified' => 1, 'locked' => 0, ); $type = $forced + $settings + $defaults; $type = (object) $type; $saved_type = node_type_save($type); node_types_rebuild(); menu_rebuild(); node_add_body_field($type); $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type))); // Reset permissions so that permissions for this content type are available. $this->checkPermissions(array(), TRUE); return $type; } /** * Get a list files that can be used in tests. * * @param $type * File type, possible values: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'. * @param $size * File size in bytes to match. Please check the tests/files folder. * @return * List of files that match filter. */ protected function drupalGetTestFiles($type, $size = NULL) { if (empty($this->generatedTestFiles)) { // Generate binary test files. $lines = array(64, 1024); $count = 0; foreach ($lines as $line) { simpletest_generate_file('binary-' . $count++, 64, $line, 'binary'); } // Generate text test files. $lines = array(16, 256, 1024, 2048, 20480); $count = 0; foreach ($lines as $line) { simpletest_generate_file('text-' . $count++, 64, $line); } // Copy other test files from simpletest. $original = drupal_get_path('module', 'simpletest') . '/files'; $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); foreach ($files as $file) { file_unmanaged_copy($file->uri, variable_get('file_public_path', conf_path() . '/files')); } $this->generatedTestFiles = TRUE; } $files = array(); // Make sure type is valid. if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) { $files = file_scan_directory('public://', '/' . $type . '\-.*/'); // If size is set then remove any files that are not of that size. if ($size !== NULL) { foreach ($files as $file) { $stats = stat($file->uri); if ($stats['size'] != $size) { unset($files[$file->uri]); } } } } usort($files, array($this, 'drupalCompareFiles')); return $files; } /** * Compare two files based on size and file name. */ protected function drupalCompareFiles($file1, $file2) { $compare_size = filesize($file1->uri) - filesize($file2->uri); if ($compare_size) { // Sort by file size. return $compare_size; } else { // The files were the same size, so sort alphabetically. return strnatcmp($file1->name, $file2->name); } } /** * Create a user with a given set of permissions. * * @param array $permissions * Array of permission names to assign to user. Note that the user always * has the default permissions derived from the "authenticated users" role. * * @return object|false * A fully loaded user object with pass_raw property, or FALSE if account * creation fails. */ protected function drupalCreateUser(array $permissions = array()) { // Create a role with the given permission set, if any. $rid = FALSE; if ($permissions) { $rid = $this->drupalCreateRole($permissions); if (!$rid) { return FALSE; } } // Create a user assigned to that role. $edit = array(); $edit['name'] = $this->randomName(); $edit['mail'] = $edit['name'] . '@example.com'; $edit['pass'] = user_password(); $edit['status'] = 1; if ($rid) { $edit['roles'] = array($rid => $rid); } $account = user_save(drupal_anonymous_user(), $edit); $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login')); if (empty($account->uid)) { return FALSE; } // Add the raw password so that we can log in as this user. $account->pass_raw = $edit['pass']; return $account; } /** * Internal helper function; Create a role with specified permissions. * * @param $permissions * Array of permission names to assign to role. * @param $name * (optional) String for the name of the role. Defaults to a random string. * @return * Role ID of newly created role, or FALSE if role creation failed. */ protected function drupalCreateRole(array $permissions, $name = NULL) { // Generate random name if it was not passed. if (!$name) { $name = $this->randomName(); } // Check the all the permissions strings are valid. if (!$this->checkPermissions($permissions)) { return FALSE; } // Create new role. $role = new stdClass(); $role->name = $name; user_role_save($role); user_role_grant_permissions($role->rid, $permissions); $this->assertTrue(isset($role->rid), t('Created role of name: @name, id: @rid', array('@name' => $name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role')); if ($role && !empty($role->rid)) { $count = db_query('SELECT COUNT(*) FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchField(); $this->assertTrue($count == count($permissions), t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role')); return $role->rid; } else { return FALSE; } } /** * Check to make sure that the array of permissions are valid. * * @param $permissions * Permissions to check. * @param $reset * Reset cached available permissions. * @return * TRUE or FALSE depending on whether the permissions are valid. */ protected function checkPermissions(array $permissions, $reset = FALSE) { $available = &drupal_static(__FUNCTION__); if (!isset($available) || $reset) { $available = array_keys(module_invoke_all('permission')); } $valid = TRUE; foreach ($permissions as $permission) { if (!in_array($permission, $available)) { $this->fail(t('Invalid permission %permission.', array('%permission' => $permission)), t('Role')); $valid = FALSE; } } return $valid; } /** * Log in a user with the internal browser. * * If a user is already logged in, then the current user is logged out before * logging in the specified user. * * Please note that neither the global $user nor the passed-in user object is * populated with data of the logged in user. If you need full access to the * user object after logging in, it must be updated manually. If you also need * access to the plain-text password of the user (set by drupalCreateUser()), * e.g. to log in the same user again, then it must be re-assigned manually. * For example: * @code * // Create a user. * $account = $this->drupalCreateUser(array()); * $this->drupalLogin($account); * // Load real user object. * $pass_raw = $account->pass_raw; * $account = user_load($account->uid); * $account->pass_raw = $pass_raw; * @endcode * * @param $account * User object representing the user to log in. * * @see drupalCreateUser() */ protected function drupalLogin(stdClass $account) { if ($this->loggedInUser) { $this->drupalLogout(); } $edit = array( 'name' => $account->name, 'pass' => $account->pass_raw ); $this->drupalPost('user', $edit, t('Log in')); // If a "log out" link appears on the page, it is almost certainly because // the login was successful. $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $account->name)), t('User login')); if ($pass) { $this->loggedInUser = $account; } } /** * Generate a token for the currently logged in user. */ protected function drupalGetToken($value = '') { $private_key = drupal_get_private_key(); return drupal_hmac_base64($value, $this->session_id . $private_key); } /* * Logs a user out of the internal browser, then check the login page to confirm logout. */ protected function drupalLogout() { // Make a request to the logout page, and redirect to the user page, the // idea being if you were properly logged out you should be seeing a login // screen. $this->drupalGet('user/logout'); $this->drupalGet('user'); $pass = $this->assertField('name', t('Username field found.'), t('Logout')); $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout')); if ($pass) { $this->loggedInUser = FALSE; } } /** * Generates a database prefix for running tests. * * The generated database table prefix is used for the Drupal installation * being performed for the test. It is also used as user agent HTTP header * value by the cURL-based browser of DrupalWebTestCase, which is sent * to the Drupal installation of the test. During early Drupal bootstrap, the * user agent HTTP header is parsed, and if it matches, all database queries * use the database table prefix that has been generated here. * * @see DrupalWebTestCase::curlInitialize() * @see drupal_valid_test_ua() * @see DrupalWebTestCase::setUp() */ protected function prepareDatabasePrefix() { $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000); // As soon as the database prefix is set, the test might start to execute. // All assertions as well as the SimpleTest batch operations are associated // with the testId, so the database prefix has to be associated with it. db_update('simpletest_test_id') ->fields(array('last_prefix' => $this->databasePrefix)) ->condition('test_id', $this->testId) ->execute(); } /** * Changes the database connection to the prefixed one. * * @see DrupalWebTestCase::setUp() */ protected function changeDatabasePrefix() { if (empty($this->databasePrefix)) { $this->prepareDatabasePrefix(); // If $this->prepareDatabasePrefix() failed to work, return without // setting $this->setupDatabasePrefix to TRUE, so setUp() methods will // know to bail out. if (empty($this->databasePrefix)) { return; } } // Clone the current connection and replace the current prefix. $connection_info = Database::getConnectionInfo('default'); Database::renameConnection('default', 'simpletest_original_default'); foreach ($connection_info as $target => $value) { $connection_info[$target]['prefix'] = array( 'default' => $value['prefix']['default'] . $this->databasePrefix, ); } Database::addConnectionInfo('default', 'default', $connection_info['default']); // Indicate the database prefix was set up correctly. $this->setupDatabasePrefix = TRUE; } /** * Prepares the current environment for running the test. * * Backups various current environment variables and resets them, so they do * not interfere with the Drupal site installation in which tests are executed * and can be restored in tearDown(). * * Also sets up new resources for the testing environment, such as the public * filesystem and configuration directories. * * @see DrupalWebTestCase::setUp() * @see DrupalWebTestCase::tearDown() */ protected function prepareEnvironment() { global $user, $language, $conf; // Store necessary current values before switching to prefixed database. $this->originalLanguage = $language; $this->originalLanguageDefault = variable_get('language_default'); $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); $this->originalProfile = drupal_get_profile(); $this->originalCleanUrl = variable_get('clean_url', 0); $this->originalUser = $user; // Set to English to prevent exceptions from utf8_truncate() from t() // during install if the current language is not 'en'. // The following array/object conversion is copied from language_default(). $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''); // Save and clean the shutdown callbacks array because it is static cached // and will be changed by the test run. Otherwise it will contain callbacks // from both environments and the testing environment will try to call the // handlers defined by the original one. $callbacks = &drupal_register_shutdown_function(); $this->originalShutdownCallbacks = $callbacks; $callbacks = array(); // Create test directory ahead of installation so fatal errors and debug // information can be logged during installation process. // Use temporary files directory with the same prefix as the database. $this->public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); $this->private_files_directory = $this->public_files_directory . '/private'; $this->temp_files_directory = $this->private_files_directory . '/temp'; // Create the directories file_prepare_directory($this->public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY); file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY); $this->generatedTestFiles = FALSE; // Log fatal errors. ini_set('log_errors', 1); ini_set('error_log', $this->public_files_directory . '/error.log'); // Set the test information for use in other parts of Drupal. $test_info = &$GLOBALS['drupal_test_info']; $test_info['test_run_id'] = $this->databasePrefix; $test_info['in_child_site'] = FALSE; // Indicate the environment was set up correctly. $this->setupEnvironment = TRUE; } /** * Sets up a Drupal site for running functional and integration tests. * * Generates a random database prefix and installs Drupal with the specified * installation profile in DrupalWebTestCase::$profile into the * prefixed database. Afterwards, installs any additional modules specified by * the test. * * After installation all caches are flushed and several configuration values * are reset to the values of the parent site executing the test, since the * default values may be incompatible with the environment in which tests are * being executed. * * @param ... * List of modules to enable for the duration of the test. This can be * either a single array or a variable number of string arguments. * * @see DrupalWebTestCase::prepareDatabasePrefix() * @see DrupalWebTestCase::changeDatabasePrefix() * @see DrupalWebTestCase::prepareEnvironment() */ protected function setUp() { global $user, $language, $conf; // Create the database prefix for this test. $this->prepareDatabasePrefix(); // Prepare the environment for running tests. $this->prepareEnvironment(); if (!$this->setupEnvironment) { return FALSE; } // Reset all statics and variables to perform tests in a clean environment. $conf = array(); drupal_static_reset(); // Change the database prefix. // All static variables need to be reset before the database prefix is // changed, since DrupalCacheArray implementations attempt to // write back to persistent caches when they are destructed. $this->changeDatabasePrefix(); if (!$this->setupDatabasePrefix) { return FALSE; } // Preset the 'install_profile' system variable, so the first call into // system_rebuild_module_data() (in drupal_install_system()) will register // the test's profile as a module. Without this, the installation profile of // the parent site (executing the test) is registered, and the test // profile's hook_install() and other hook implementations are never invoked. $conf['install_profile'] = $this->profile; // Perform the actual Drupal installation. include_once DRUPAL_ROOT . '/includes/install.inc'; drupal_install_system(); $this->preloadRegistry(); // Set path variables. variable_set('file_public_path', $this->public_files_directory); variable_set('file_private_path', $this->private_files_directory); variable_set('file_temporary_path', $this->temp_files_directory); // Set the 'simpletest_parent_profile' variable to add the parent profile's // search path to the child site's search paths. // @see drupal_system_listing() // @todo This may need to be primed like 'install_profile' above. variable_set('simpletest_parent_profile', $this->originalProfile); // Include the testing profile. variable_set('install_profile', $this->profile); $profile_details = install_profile_info($this->profile, 'en'); // Install the modules specified by the testing profile. module_enable($profile_details['dependencies'], FALSE); // Install modules needed for this test. This could have been passed in as // either a single array argument or a variable number of string arguments. // @todo Remove this compatibility layer in Drupal 8, and only accept // $modules as a single array argument. $modules = func_get_args(); if (isset($modules[0]) && is_array($modules[0])) { $modules = $modules[0]; } if ($modules) { $success = module_enable($modules, TRUE); $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules)))); } // Run the profile tasks. $install_profile_module_exists = db_query("SELECT 1 FROM {system} WHERE type = 'module' AND name = :name", array( ':name' => $this->profile, ))->fetchField(); if ($install_profile_module_exists) { module_enable(array($this->profile), FALSE); } // Reset/rebuild all data structures after enabling the modules. $this->resetAll(); // Run cron once in that environment, as install.php does at the end of // the installation process. drupal_cron_run(); // Ensure that the session is not written to the new environment and replace // the global $user session with uid 1 from the new test site. drupal_save_session(FALSE); // Login as uid 1. $user = user_load(1); // Restore necessary variables. variable_set('install_task', 'done'); variable_set('clean_url', $this->originalCleanUrl); variable_set('site_mail', 'simpletest@example.com'); variable_set('date_default_timezone', date_default_timezone_get()); // Set up English language. unset($conf['language_default']); $language = language_default(); // Use the test mail class instead of the default mail handler class. variable_set('mail_system', array('default-system' => 'TestingMailSystem')); drupal_set_time_limit($this->timeLimit); $this->setup = TRUE; } /** * Preload the registry from the testing site. * * This method is called by DrupalWebTestCase::setUp(), and preloads the * registry from the testing site to cut down on the time it takes to * set up a clean environment for the current test run. */ protected function preloadRegistry() { // Use two separate queries, each with their own connections: copy the // {registry} and {registry_file} tables over from the parent installation // to the child installation. $original_connection = Database::getConnection('default', 'simpletest_original_default'); $test_connection = Database::getConnection(); foreach (array('registry', 'registry_file') as $table) { // Find the records from the parent database. $source_query = $original_connection ->select($table, array(), array('fetch' => PDO::FETCH_ASSOC)) ->fields($table); $dest_query = $test_connection->insert($table); $first = TRUE; foreach ($source_query->execute() as $row) { if ($first) { $dest_query->fields(array_keys($row)); $first = FALSE; } // Insert the records into the child database. $dest_query->values($row); } $dest_query->execute(); } } /** * Reset all data structures after having enabled new modules. * * This method is called by DrupalWebTestCase::setUp() after enabling * the requested modules. It must be called again when additional modules * are enabled later. */ protected function resetAll() { // Reset all static variables. drupal_static_reset(); // Reset the list of enabled modules. module_list(TRUE); // Reset cached schema for new database prefix. This must be done before // drupal_flush_all_caches() so rebuilds can make use of the schema of // modules enabled on the cURL side. drupal_get_schema(NULL, TRUE); // Perform rebuilds and flush remaining caches. drupal_flush_all_caches(); // Reload global $conf array and permissions. $this->refreshVariables(); $this->checkPermissions(array(), TRUE); } /** * Refresh the in-memory set of variables. Useful after a page request is made * that changes a variable in a different thread. * * In other words calling a settings page with $this->drupalPost() with a changed * value would update a variable to reflect that change, but in the thread that * made the call (thread running the test) the changed variable would not be * picked up. * * This method clears the variables cache and loads a fresh copy from the database * to ensure that the most up-to-date set of variables is loaded. */ protected function refreshVariables() { global $conf; cache_clear_all('variables', 'cache_bootstrap'); $conf = variable_initialize(); } /** * Delete created files and temporary files directory, delete the tables created by setUp(), * and reset the database prefix. */ protected function tearDown() { global $user, $language; // In case a fatal error occurred that was not in the test process read the // log to pick up any fatal errors. simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE); $emailCount = count(variable_get('drupal_test_email_collector', array())); if ($emailCount) { $message = format_plural($emailCount, '1 e-mail was sent during this test.', '@count e-mails were sent during this test.'); $this->pass($message, t('E-mail')); } // Delete temporary files directory. file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); // Remove all prefixed tables. $tables = db_find_tables($this->databasePrefix . '%'); $connection_info = Database::getConnectionInfo('default'); $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%'); if (empty($tables)) { $this->fail('Failed to find test tables to drop.'); } $prefix_length = strlen($connection_info['default']['prefix']['default']); foreach ($tables as $table) { if (db_drop_table(substr($table, $prefix_length))) { unset($tables[$table]); } } if (!empty($tables)) { $this->fail('Failed to drop all prefixed tables.'); } // Get back to the original connection. Database::removeConnection('default'); Database::renameConnection('simpletest_original_default', 'default'); // Restore original shutdown callbacks array to prevent original // environment of calling handlers from test run. $callbacks = &drupal_register_shutdown_function(); $callbacks = $this->originalShutdownCallbacks; // Return the user to the original one. $user = $this->originalUser; drupal_save_session(TRUE); // Ensure that internal logged in variable and cURL options are reset. $this->loggedInUser = FALSE; $this->additionalCurlOptions = array(); // Reload module list and implementations to ensure that test module hooks // aren't called after tests. module_list(TRUE); module_implements('', FALSE, TRUE); // Reset the Field API. field_cache_clear(); // Rebuild caches. $this->refreshVariables(); // Reset public files directory. $GLOBALS['conf']['file_public_path'] = $this->originalFileDirectory; // Reset language. $language = $this->originalLanguage; if ($this->originalLanguageDefault) { $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; } // Close the CURL handler. $this->curlClose(); } /** * Initializes the cURL connection. * * If the simpletest_httpauth_credentials variable is set, this function will * add HTTP authentication headers. This is necessary for testing sites that * are protected by login credentials from public access. * See the description of $curl_options for other options. */ protected function curlInitialize() { global $base_url; if (!isset($this->curlHandle)) { $this->curlHandle = curl_init(); // Some versions/configurations of cURL break on a NULL cookie jar, so // supply a real file. if (empty($this->cookieFile)) { $this->cookieFile = $this->public_files_directory . '/cookie.jar'; } $curl_options = array( CURLOPT_COOKIEJAR => $this->cookieFile, CURLOPT_URL => $base_url, CURLOPT_FOLLOWLOCATION => FALSE, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on HTTPS. CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on HTTPS. CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'), CURLOPT_USERAGENT => $this->databasePrefix, ); if (isset($this->httpauth_credentials)) { $curl_options[CURLOPT_HTTPAUTH] = $this->httpauth_method; $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials; } // curl_setopt_array() returns FALSE if any of the specified options // cannot be set, and stops processing any further options. $result = curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); if (!$result) { throw new Exception('One or more cURL options could not be set.'); } // By default, the child session name should be the same as the parent. $this->session_name = session_name(); } // We set the user agent header on each request so as to use the current // time and a new uniqid. if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) { curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0])); } } /** * Initializes and executes a cURL request. * * @param $curl_options * An associative array of cURL options to set, where the keys are constants * defined by the cURL library. For a list of valid options, see * http://www.php.net/manual/function.curl-setopt.php * @param $redirect * FALSE if this is an initial request, TRUE if this request is the result * of a redirect. * * @return * The content returned from the call to curl_exec(). * * @see curlInitialize() */ protected function curlExec($curl_options, $redirect = FALSE) { $this->curlInitialize(); // cURL incorrectly handles URLs with a fragment by including the // fragment in the request to the server, causing some web servers // to reject the request citing "400 - Bad Request". To prevent // this, we strip the fragment from the request. // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0. if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) { $original_url = $curl_options[CURLOPT_URL]; $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#'); } $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; if (!empty($curl_options[CURLOPT_POST])) { // This is a fix for the Curl library to prevent Expect: 100-continue // headers in POST requests, that may cause unexpected HTTP response // codes from some webservers (like lighttpd that returns a 417 error // code). It is done by setting an empty "Expect" header field that is // not overwritten by Curl. $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:'; } curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); if (!$redirect) { // Reset headers, the session ID and the redirect counter. $this->session_id = NULL; $this->headers = array(); $this->redirect_count = 0; } $content = curl_exec($this->curlHandle); $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); // cURL incorrectly handles URLs with fragments, so instead of // letting cURL handle redirects we take of them ourselves to // to prevent fragments being sent to the web server as part // of the request. // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0. if (in_array($status, array(300, 301, 302, 303, 305, 307)) && $this->redirect_count < variable_get('simpletest_maximum_redirects', 5)) { if ($this->drupalGetHeader('location')) { $this->redirect_count++; $curl_options = array(); $curl_options[CURLOPT_URL] = $this->drupalGetHeader('location'); $curl_options[CURLOPT_HTTPGET] = TRUE; return $this->curlExec($curl_options, TRUE); } } $this->drupalSetContent($content, isset($original_url) ? $original_url : curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL)); $message_vars = array( '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'), '@url' => isset($original_url) ? $original_url : $url, '@status' => $status, '!length' => format_size(strlen($this->drupalGetContent())) ); $message = t('!method @url returned @status (!length).', $message_vars); $this->assertTrue($this->drupalGetContent() !== FALSE, $message, t('Browser')); return $this->drupalGetContent(); } /** * Reads headers and registers errors received from the tested site. * * @see _drupal_log_error(). * * @param $curlHandler * The cURL handler. * @param $header * An header. */ protected function curlHeaderCallback($curlHandler, $header) { // Header fields can be extended over multiple lines by preceding each // extra line with at least one SP or HT. They should be joined on receive. // Details are in RFC2616 section 4. if ($header[0] == ' ' || $header[0] == "\t") { // Normalize whitespace between chucks. $this->headers[] = array_pop($this->headers) . ' ' . trim($header); } else { $this->headers[] = $header; } // Errors are being sent via X-Drupal-Assertion-* headers, // generated by _drupal_log_error() in the exact form required // by DrupalWebTestCase::error(). if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) { // Call DrupalWebTestCase::error() with the parameters from the header. call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1]))); } // Save cookies. if (preg_match('/^Set-Cookie: ([^=]+)=(.+)/', $header, $matches)) { $name = $matches[1]; $parts = array_map('trim', explode(';', $matches[2])); $value = array_shift($parts); $this->cookies[$name] = array('value' => $value, 'secure' => in_array('secure', $parts)); if ($name == $this->session_name) { if ($value != 'deleted') { $this->session_id = $value; } else { $this->session_id = NULL; } } } // This is required by cURL. return strlen($header); } /** * Close the cURL handler and unset the handler. */ protected function curlClose() { if (isset($this->curlHandle)) { curl_close($this->curlHandle); unset($this->curlHandle); } } /** * Parse content returned from curlExec using DOM and SimpleXML. * * @return * A SimpleXMLElement or FALSE on failure. */ protected function parse() { if (!$this->elements) { // DOM can load HTML soup. But, HTML soup can throw warnings, suppress // them. $htmlDom = new DOMDocument(); @$htmlDom->loadHTML($this->drupalGetContent()); if ($htmlDom) { $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser')); // It's much easier to work with simplexml than DOM, luckily enough // we can just simply import our DOM tree. $this->elements = simplexml_import_dom($htmlDom); } } if (!$this->elements) { $this->fail(t('Parsed page successfully.'), t('Browser')); } return $this->elements; } /** * Retrieves a Drupal path or an absolute path. * * @param $path * Drupal path or URL to load into internal browser * @param $options * Options to be forwarded to url(). * @param $headers * An array containing additional HTTP request headers, each formatted as * "name: value". * @return * The retrieved HTML string, also available as $this->drupalGetContent() */ protected function drupalGet($path, array $options = array(), array $headers = array()) { $options['absolute'] = TRUE; // We re-using a CURL connection here. If that connection still has certain // options set, it might change the GET into a POST. Make sure we clear out // previous options. $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers)); $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. // Replace original page output with new output from redirected page(s). if ($new = $this->checkForMetaRefresh()) { $out = $new; } $this->verbose('GET request to: ' . $path . '
      Ending URL: ' . $this->getUrl() . '
      ' . $out); return $out; } /** * Retrieve a Drupal path or an absolute path and JSON decode the result. */ protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) { return drupal_json_decode($this->drupalGet($path, $options, $headers)); } /** * Execute a POST request on a Drupal page. * It will be done as usual POST request with SimpleBrowser. * * @param $path * Location of the post form. Either a Drupal path or an absolute path or * NULL to post to the current page. For multi-stage forms you can set the * path to NULL and have it post to the last received page. Example: * * @code * // First step in form. * $edit = array(...); * $this->drupalPost('some_url', $edit, t('Save')); * * // Second step in form. * $edit = array(...); * $this->drupalPost(NULL, $edit, t('Save')); * @endcode * @param $edit * Field data in an associative array. Changes the current input fields * (where possible) to the values indicated. A checkbox can be set to * TRUE to be checked and FALSE to be unchecked. Note that when a form * contains file upload fields, other fields cannot start with the '@' * character. * * Multiple select fields can be set using name[] and setting each of the * possible values. Example: * @code * $edit = array(); * $edit['name[]'] = array('value1', 'value2'); * @endcode * @param $submit * Value of the submit button whose click is to be emulated. For example, * t('Save'). The processing of the request depends on this value. For * example, a form may have one button with the value t('Save') and another * button with the value t('Delete'), and execute different code depending * on which one is clicked. * * This function can also be called to emulate an Ajax submission. In this * case, this value needs to be an array with the following keys: * - path: A path to submit the form values to for Ajax-specific processing, * which is likely different than the $path parameter used for retrieving * the initial form. Defaults to 'system/ajax'. * - triggering_element: If the value for the 'path' key is 'system/ajax' or * another generic Ajax processing path, this needs to be set to the name * of the element. If the name doesn't identify the element uniquely, then * this should instead be an array with a single key/value pair, * corresponding to the element name and value. The callback for the * generic Ajax processing path uses this to find the #ajax information * for the element, including which specific callback to use for * processing the request. * * This can also be set to NULL in order to emulate an Internet Explorer * submission of a form with a single text field, and pressing ENTER in that * textfield: under these conditions, no button information is added to the * POST data. * @param $options * Options to be forwarded to url(). * @param $headers * An array containing additional HTTP request headers, each formatted as * "name: value". * @param $form_html_id * (optional) HTML ID of the form to be submitted. On some pages * there are many identical forms, so just using the value of the submit * button is not enough. For example: 'trigger-node-presave-assign-form'. * Note that this is not the Drupal $form_id, but rather the HTML ID of the * form, which is typically the same thing but with hyphens replacing the * underscores. * @param $extra_post * (optional) A string of additional data to append to the POST submission. * This can be used to add POST data for which there are no HTML fields, as * is done by drupalPostAJAX(). This string is literally appended to the * POST data, so it must already be urlencoded and contain a leading "&" * (e.g., "&extra_var1=hello+world&extra_var2=you%26me"). */ protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) { $submit_matches = FALSE; $ajax = is_array($submit); if (isset($path)) { $this->drupalGet($path, $options); } if ($this->parse()) { $edit_save = $edit; // Let's iterate over all the forms. $xpath = "//form"; if (!empty($form_html_id)) { $xpath .= "[@id='" . $form_html_id . "']"; } $forms = $this->xpath($xpath); foreach ($forms as $form) { // We try to set the fields of this form as specified in $edit. $edit = $edit_save; $post = array(); $upload = array(); $submit_matches = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form); $action = isset($form['action']) ? $this->getAbsoluteUrl((string) $form['action']) : $this->getUrl(); if ($ajax) { $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax'); // Ajax callbacks verify the triggering element if necessary, so while // we may eventually want extra code that verifies it in the // handleForm() function, it's not currently a requirement. $submit_matches = TRUE; } // We post only if we managed to handle every field in edit and the // submit button matches. if (!$edit && ($submit_matches || !isset($submit))) { $post_array = $post; if ($upload) { // TODO: cURL handles file uploads for us, but the implementation // is broken. This is a less than elegant workaround. Alternatives // are being explored at #253506. foreach ($upload as $key => $file) { $file = drupal_realpath($file); if ($file && is_file($file)) { // Use the new CurlFile class for file uploads when using PHP // 5.5 or higher. if (class_exists('CurlFile')) { $post[$key] = curl_file_create($file); } else { $post[$key] = '@' . $file; } } } } else { foreach ($post as $key => $value) { // Encode according to application/x-www-form-urlencoded // Both names and values needs to be urlencoded, according to // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 $post[$key] = urlencode($key) . '=' . urlencode($value); } $post = implode('&', $post) . $extra_post; } $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers)); // Ensure that any changes to variables in the other thread are picked up. $this->refreshVariables(); // Replace original page output with new output from redirected page(s). if ($new = $this->checkForMetaRefresh()) { $out = $new; } $this->verbose('POST request to: ' . $path . '
      Ending URL: ' . $this->getUrl() . '
      Fields: ' . highlight_string('' . $out); return $out; } } // We have not found a form which contained all fields of $edit. foreach ($edit as $name => $value) { $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value))); } if (!$ajax && isset($submit)) { $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit))); } $this->fail(t('Found the requested form fields at @path', array('@path' => $path))); } } /** * Execute an Ajax submission. * * This executes a POST as ajax.js does. It uses the returned JSON data, an * array of commands, to update $this->content using equivalent DOM * manipulation as is used by ajax.js. It also returns the array of commands. * * @param $path * Location of the form containing the Ajax enabled element to test. Can be * either a Drupal path or an absolute path or NULL to use the current page. * @param $edit * Field data in an associative array. Changes the current input fields * (where possible) to the values indicated. * @param $triggering_element * The name of the form element that is responsible for triggering the Ajax * functionality to test. May be a string or, if the triggering element is * a button, an associative array where the key is the name of the button * and the value is the button label. i.e.) array('op' => t('Refresh')). * @param $ajax_path * (optional) Override the path set by the Ajax settings of the triggering * element. In the absence of both the triggering element's Ajax path and * $ajax_path 'system/ajax' will be used. * @param $options * (optional) Options to be forwarded to url(). * @param $headers * (optional) An array containing additional HTTP request headers, each * formatted as "name: value". Forwarded to drupalPost(). * @param $form_html_id * (optional) HTML ID of the form to be submitted, use when there is more * than one identical form on the same page and the value of the triggering * element is not enough to identify the form. Note this is not the Drupal * ID of the form but rather the HTML ID of the form. * @param $ajax_settings * (optional) An array of Ajax settings which if specified will be used in * place of the Ajax settings of the triggering element. * * @return * An array of Ajax commands. * * @see drupalPost() * @see ajax.js */ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path = NULL, array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = NULL) { // Get the content of the initial page prior to calling drupalPost(), since // drupalPost() replaces $this->content. if (isset($path)) { $this->drupalGet($path, $options); } $content = $this->content; $drupal_settings = $this->drupalSettings; // Get the Ajax settings bound to the triggering element. if (!isset($ajax_settings)) { if (is_array($triggering_element)) { $xpath = '//*[@name="' . key($triggering_element) . '" and @value="' . current($triggering_element) . '"]'; } else { $xpath = '//*[@name="' . $triggering_element . '"]'; } if (isset($form_html_id)) { $xpath = '//form[@id="' . $form_html_id . '"]' . $xpath; } $element = $this->xpath($xpath); $element_id = (string) $element[0]['id']; $ajax_settings = $drupal_settings['ajax'][$element_id]; } // Add extra information to the POST data as ajax.js does. $extra_post = ''; if (isset($ajax_settings['submit'])) { foreach ($ajax_settings['submit'] as $key => $value) { $extra_post .= '&' . urlencode($key) . '=' . urlencode($value); } } foreach ($this->xpath('//*[@id]') as $element) { $id = (string) $element['id']; $extra_post .= '&' . urlencode('ajax_html_ids[]') . '=' . urlencode($id); } if (isset($drupal_settings['ajaxPageState'])) { $extra_post .= '&' . urlencode('ajax_page_state[theme]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme']); $extra_post .= '&' . urlencode('ajax_page_state[theme_token]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme_token']); foreach ($drupal_settings['ajaxPageState']['css'] as $key => $value) { $extra_post .= '&' . urlencode("ajax_page_state[css][$key]") . '=1'; } foreach ($drupal_settings['ajaxPageState']['js'] as $key => $value) { $extra_post .= '&' . urlencode("ajax_page_state[js][$key]") . '=1'; } } // Unless a particular path is specified, use the one specified by the // Ajax settings, or else 'system/ajax'. if (!isset($ajax_path)) { $ajax_path = isset($ajax_settings['url']) ? $ajax_settings['url'] : 'system/ajax'; } // Submit the POST request. $return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post)); // Change the page content by applying the returned commands. if (!empty($ajax_settings) && !empty($return)) { // ajax.js applies some defaults to the settings object, so do the same // for what's used by this function. $ajax_settings += array( 'method' => 'replaceWith', ); // DOM can load HTML soup. But, HTML soup can throw warnings, suppress // them. $dom = new DOMDocument(); @$dom->loadHTML($content); // XPath allows for finding wrapper nodes better than DOM does. $xpath = new DOMXPath($dom); foreach ($return as $command) { switch ($command['command']) { case 'settings': $drupal_settings = drupal_array_merge_deep($drupal_settings, $command['settings']); break; case 'insert': $wrapperNode = NULL; // When a command doesn't specify a selector, use the // #ajax['wrapper'] which is always an HTML ID. if (!isset($command['selector'])) { $wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0); } // @todo Ajax commands can target any jQuery selector, but these are // hard to fully emulate with XPath. For now, just handle 'head' // and 'body', since these are used by ajax_render(). elseif (in_array($command['selector'], array('head', 'body'))) { $wrapperNode = $xpath->query('//' . $command['selector'])->item(0); } if ($wrapperNode) { // ajax.js adds an enclosing DIV to work around a Safari bug. $newDom = new DOMDocument(); $newDom->loadHTML('
      ' . $command['data'] . '
      '); $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE); $method = isset($command['method']) ? $command['method'] : $ajax_settings['method']; // The "method" is a jQuery DOM manipulation function. Emulate // each one using PHP's DOMNode API. switch ($method) { case 'replaceWith': $wrapperNode->parentNode->replaceChild($newNode, $wrapperNode); break; case 'append': $wrapperNode->appendChild($newNode); break; case 'prepend': // If no firstChild, insertBefore() falls back to // appendChild(). $wrapperNode->insertBefore($newNode, $wrapperNode->firstChild); break; case 'before': $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode); break; case 'after': // If no nextSibling, insertBefore() falls back to // appendChild(). $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling); break; case 'html': foreach ($wrapperNode->childNodes as $childNode) { $wrapperNode->removeChild($childNode); } $wrapperNode->appendChild($newNode); break; } } break; // @todo Add suitable implementations for these commands in order to // have full test coverage of what ajax.js can do. case 'remove': break; case 'changed': break; case 'css': break; case 'data': break; case 'restripe': break; } } $content = $dom->saveHTML(); } $this->drupalSetContent($content); $this->drupalSetSettings($drupal_settings); return $return; } /** * Runs cron in the Drupal installed by Simpletest. */ protected function cronRun() { $this->drupalGet($GLOBALS['base_url'] . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal')))); } /** * Check for meta refresh tag and if found call drupalGet() recursively. This * function looks for the http-equiv attribute to be set to "Refresh" * and is case-sensitive. * * @return * Either the new page content or FALSE. */ protected function checkForMetaRefresh() { if (strpos($this->drupalGetContent(), 'parse()) { $refresh = $this->xpath('//meta[@http-equiv="Refresh"]'); if (!empty($refresh)) { // Parse the content attribute of the meta tag for the format: // "[delay]: URL=[page_to_redirect_to]". if (preg_match('/\d+;\s*URL=(?P.*)/i', $refresh[0]['content'], $match)) { return $this->drupalGet($this->getAbsoluteUrl(decode_entities($match['url']))); } } } return FALSE; } /** * Retrieves only the headers for a Drupal path or an absolute path. * * @param $path * Drupal path or URL to load into internal browser * @param $options * Options to be forwarded to url(). * @param $headers * An array containing additional HTTP request headers, each formatted as * "name: value". * @return * The retrieved headers, also available as $this->drupalGetContent() */ protected function drupalHead($path, array $options = array(), array $headers = array()) { $options['absolute'] = TRUE; $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_HTTPHEADER => $headers)); $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. return $out; } /** * Handle form input related to drupalPost(). Ensure that the specified fields * exist and attempt to create POST data in the correct manner for the particular * field type. * * @param $post * Reference to array of post values. * @param $edit * Reference to array of edit values to be checked against the form. * @param $submit * Form submit button value. * @param $form * Array of form elements. * @return * Submit value matches a valid submit input in the form. */ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) { // Retrieve the form elements. $elements = $form->xpath('.//input[not(@disabled)]|.//textarea[not(@disabled)]|.//select[not(@disabled)]'); $submit_matches = FALSE; foreach ($elements as $element) { // SimpleXML objects need string casting all the time. $name = (string) $element['name']; // This can either be the type of or the name of the tag itself // for '; $output .= ''; return $output; } /** * Returns HTML for a password form element. * * @param $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #value, #description, #size, #maxlength, * #required, #attributes. * * @ingroup themeable */ function theme_password($variables) { $element = $variables['element']; $element['#attributes']['type'] = 'password'; element_set_attributes($element, array('id', 'name', 'size', 'maxlength')); _form_set_class($element, array('form-text')); return ''; } /** * Expands a weight element into a select element. */ function form_process_weight($element) { $element['#is_weight'] = TRUE; // If the number of options is small enough, use a select field. $max_elements = variable_get('drupal_weight_select_max', DRUPAL_WEIGHT_SELECT_MAX); if ($element['#delta'] <= $max_elements) { $element['#type'] = 'select'; for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { $weights[$n] = $n; } $element['#options'] = $weights; $element += element_info('select'); } // Otherwise, use a text field. else { $element['#type'] = 'textfield'; // Use a field big enough to fit most weights. $element['#size'] = 10; $element['#element_validate'] = array('element_validate_integer'); $element += element_info('textfield'); } return $element; } /** * Returns HTML for a file upload form element. * * For assistance with handling the uploaded file correctly, see the API * provided by file.inc. * * @param $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #name, #size, #description, #required, * #attributes. * * @ingroup themeable */ function theme_file($variables) { $element = $variables['element']; $element['#attributes']['type'] = 'file'; element_set_attributes($element, array('id', 'name', 'size')); _form_set_class($element, array('form-file')); return ''; } /** * Returns HTML for a form element. * * Each form element is wrapped in a DIV container having the following CSS * classes: * - form-item: Generic for all form elements. * - form-type-#type: The internal element #type. * - form-item-#name: The internal form element #name (usually derived from the * $form structure and set via form_builder()). * - form-disabled: Only set if the form element is #disabled. * * In addition to the element itself, the DIV contains a label for the element * based on the optional #title_display property, and an optional #description. * * The optional #title_display property can have these values: * - before: The label is output before the element. This is the default. * The label includes the #title and the required marker, if #required. * - after: The label is output after the element. For example, this is used * for radio and checkbox #type elements as set in system_element_info(). * If the #title is empty but the field is #required, the label will * contain only the required marker. * - invisible: Labels are critical for screen readers to enable them to * properly navigate through forms but can be visually distracting. This * property hides the label for everyone except screen readers. * - attribute: Set the title attribute on the element to create a tooltip * but output no label element. This is supported only for checkboxes * and radios in form_pre_render_conditional_form_element(). It is used * where a visual label is not needed, such as a table of checkboxes where * the row and column provide the context. The tooltip will include the * title and required marker. * * If the #title property is not set, then the label and any required marker * will not be output, regardless of the #title_display or #required values. * This can be useful in cases such as the password_confirm element, which * creates children elements that have their own labels and required markers, * but the parent element should have neither. Use this carefully because a * field without an associated label can cause accessibility challenges. * * @param $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #title_display, #description, #id, #required, * #children, #type, #name. * * @ingroup themeable */ function theme_form_element($variables) { $element = &$variables['element']; // This function is invoked as theme wrapper, but the rendered form element // may not necessarily have been processed by form_builder(). $element += array( '#title_display' => 'before', ); // Add element #id for #type 'item'. if (isset($element['#markup']) && !empty($element['#id'])) { $attributes['id'] = $element['#id']; } // Add element's #type and #name as class to aid with JS/CSS selectors. $attributes['class'] = array('form-item'); if (!empty($element['#type'])) { $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-'); } if (!empty($element['#name'])) { $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); } // Add a class for disabled elements to facilitate cross-browser styling. if (!empty($element['#attributes']['disabled'])) { $attributes['class'][] = 'form-disabled'; } $output = '' . "\n"; // If #title is not set, we don't display any label or required marker. if (!isset($element['#title'])) { $element['#title_display'] = 'none'; } $prefix = isset($element['#field_prefix']) ? '' . $element['#field_prefix'] . ' ' : ''; $suffix = isset($element['#field_suffix']) ? ' ' . $element['#field_suffix'] . '' : ''; switch ($element['#title_display']) { case 'before': case 'invisible': $output .= ' ' . theme('form_element_label', $variables); $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; break; case 'after': $output .= ' ' . $prefix . $element['#children'] . $suffix; $output .= ' ' . theme('form_element_label', $variables) . "\n"; break; case 'none': case 'attribute': // Output no label and no required marker, only the children. $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; break; } if (!empty($element['#description'])) { $output .= '
      ' . $element['#description'] . "
      \n"; } $output .= "\n"; return $output; } /** * Returns HTML for a marker for required form elements. * * @param $variables * An associative array containing: * - element: An associative array containing the properties of the element. * * @ingroup themeable */ function theme_form_required_marker($variables) { // This is also used in the installer, pre-database setup. $t = get_t(); $attributes = array( 'class' => 'form-required', 'title' => $t('This field is required.'), ); return '*'; } /** * Returns HTML for a form element label and required marker. * * Form element labels include the #title and a #required marker. The label is * associated with the element itself by the element #id. Labels may appear * before or after elements, depending on theme_form_element() and * #title_display. * * This function will not be called for elements with no labels, depending on * #title_display. For elements that have an empty #title and are not required, * this function will output no label (''). For required elements that have an * empty #title, this will output the required marker alone within the label. * The label will use the #id to associate the marker with the field that is * required. That is especially important for screenreader users to know * which field is required. * * @param $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #required, #title, #id, #value, #description. * * @ingroup themeable */ function theme_form_element_label($variables) { $element = $variables['element']; // This is also used in the installer, pre-database setup. $t = get_t(); // If title and required marker are both empty, output no label. if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) { return ''; } // If the element is required, a required marker is appended to the label. $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : ''; $title = filter_xss_admin($element['#title']); $attributes = array(); // Style the label as class option to display inline with the element. if ($element['#title_display'] == 'after') { $attributes['class'] = 'option'; } // Show label only to screen readers to avoid disruption in visual flows. elseif ($element['#title_display'] == 'invisible') { $attributes['class'] = 'element-invisible'; } if (!empty($element['#id'])) { $attributes['for'] = $element['#id']; } // The leading whitespace helps visually separate fields from inline labels. return ' ' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "\n"; } /** * Sets a form element's class attribute. * * Adds 'required' and 'error' classes as needed. * * @param $element * The form element. * @param $name * Array of new class names to be added. */ function _form_set_class(&$element, $class = array()) { if (!empty($class)) { if (!isset($element['#attributes']['class'])) { $element['#attributes']['class'] = array(); } $element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class); } // This function is invoked from form element theme functions, but the // rendered form element may not necessarily have been processed by // form_builder(). if (!empty($element['#required'])) { $element['#attributes']['class'][] = 'required'; } if (isset($element['#parents']) && form_get_error($element) !== NULL && !empty($element['#validated'])) { $element['#attributes']['class'][] = 'error'; } } /** * Form element validation handler for integer elements. */ function element_validate_integer($element, &$form_state) { $value = $element['#value']; if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) { form_error($element, t('%name must be an integer.', array('%name' => $element['#title']))); } } /** * Form element validation handler for integer elements that must be positive. */ function element_validate_integer_positive($element, &$form_state) { $value = $element['#value']; if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) { form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title']))); } } /** * Form element validation handler for number elements. */ function element_validate_number($element, &$form_state) { $value = $element['#value']; if ($value != '' && !is_numeric($value)) { form_error($element, t('%name must be a number.', array('%name' => $element['#title']))); } } /** * @} End of "defgroup form_api". */ /** * @defgroup batch Batch operations * @{ * Creates and processes batch operations. * * Functions allowing forms processing to be spread out over several page * requests, thus ensuring that the processing does not get interrupted * because of a PHP timeout, while allowing the user to receive feedback * on the progress of the ongoing operations. * * The API is primarily designed to integrate nicely with the Form API * workflow, but can also be used by non-Form API scripts (like update.php) * or even simple page callbacks (which should probably be used sparingly). * * Example: * @code * $batch = array( * 'title' => t('Exporting'), * 'operations' => array( * array('my_function_1', array($account->uid, 'story')), * array('my_function_2', array()), * ), * 'finished' => 'my_finished_callback', * 'file' => 'path_to_file_containing_myfunctions', * ); * batch_set($batch); * // Only needed if not inside a form _submit handler. * // Setting redirect in batch_process. * batch_process('node/1'); * @endcode * * Note: if the batch 'title', 'init_message', 'progress_message', or * 'error_message' could contain any user input, it is the responsibility of * the code calling batch_set() to sanitize them first with a function like * check_plain() or filter_xss(). Furthermore, if the batch operation * returns any user input in the 'results' or 'message' keys of $context, * it must also sanitize them first. * * Sample batch operations: * @code * // Simple and artificial: load a node of a given type for a given user * function my_function_1($uid, $type, &$context) { * // The $context array gathers batch context information about the execution (read), * // as well as 'return values' for the current operation (write) * // The following keys are provided : * // 'results' (read / write): The array of results gathered so far by * // the batch processing, for the current operation to append its own. * // 'message' (write): A text message displayed in the progress page. * // The following keys allow for multi-step operations : * // 'sandbox' (read / write): An array that can be freely used to * // store persistent data between iterations. It is recommended to * // use this instead of $_SESSION, which is unsafe if the user * // continues browsing in a separate window while the batch is processing. * // 'finished' (write): A float number between 0 and 1 informing * // the processing engine of the completion level for the operation. * // 1 (or no value explicitly set) means the operation is finished * // and the batch processing can continue to the next operation. * * $node = node_load(array('uid' => $uid, 'type' => $type)); * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); * $context['message'] = check_plain($node->title); * } * * // More advanced example: multi-step operation - load all nodes, five by five * function my_function_2(&$context) { * if (empty($context['sandbox'])) { * $context['sandbox']['progress'] = 0; * $context['sandbox']['current_node'] = 0; * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); * } * $limit = 5; * $result = db_select('node') * ->fields('node', array('nid')) * ->condition('nid', $context['sandbox']['current_node'], '>') * ->orderBy('nid') * ->range(0, $limit) * ->execute(); * foreach ($result as $row) { * $node = node_load($row->nid, NULL, TRUE); * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); * $context['sandbox']['progress']++; * $context['sandbox']['current_node'] = $node->nid; * $context['message'] = check_plain($node->title); * } * if ($context['sandbox']['progress'] != $context['sandbox']['max']) { * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; * } * } * @endcode * * Sample 'finished' callback: * @code * function batch_test_finished($success, $results, $operations) { * // The 'success' parameter means no fatal PHP errors were detected. All * // other error management should be handled using 'results'. * if ($success) { * $message = format_plural(count($results), 'One post processed.', '@count posts processed.'); * } * else { * $message = t('Finished with an error.'); * } * drupal_set_message($message); * // Providing data for the redirected page is done through $_SESSION. * foreach ($results as $result) { * $items[] = t('Loaded node %title.', array('%title' => $result)); * } * $_SESSION['my_batch_results'] = $items; * } * @endcode */ /** * Adds a new batch. * * Batch operations are added as new batch sets. Batch sets are used to spread * processing (primarily, but not exclusively, forms processing) over several * page requests. This helps to ensure that the processing is not interrupted * due to PHP timeouts, while users are still able to receive feedback on the * progress of the ongoing operations. Combining related operations into * distinct batch sets provides clean code independence for each batch set, * ensuring that two or more batches, submitted independently, can be processed * without mutual interference. Each batch set may specify its own set of * operations and results, produce its own UI messages, and trigger its own * 'finished' callback. Batch sets are processed sequentially, with the progress * bar starting afresh for each new set. * * @param $batch_definition * An associative array defining the batch, with the following elements (all * are optional except as noted): * - operations: (required) Array of function calls to be performed. * Example: * @code * array( * array('my_function_1', array($arg1)), * array('my_function_2', array($arg2_1, $arg2_2)), * ) * @endcode * - title: A safe, translated string to use as the title for the progress * page. Defaults to t('Processing'). * - init_message: Message displayed while the processing is initialized. * Defaults to t('Initializing.'). * - progress_message: Message displayed while processing the batch. Available * placeholders are @current, @remaining, @total, @percentage, @estimate and * @elapsed. Defaults to t('Completed @current of @total.'). * - error_message: Message displayed if an error occurred while processing * the batch. Defaults to t('An error has occurred.'). * - finished: Name of a function to be executed after the batch has * completed. This should be used to perform any result massaging that may * be needed, and possibly save data in $_SESSION for display after final * page redirection. * - file: Path to the file containing the definitions of the 'operations' and * 'finished' functions, for instance if they don't reside in the main * .module file. The path should be relative to base_path(), and thus should * be built using drupal_get_path(). * - css: Array of paths to CSS files to be used on the progress page. * - url_options: options passed to url() when constructing redirect URLs for * the batch. */ function batch_set($batch_definition) { if ($batch_definition) { $batch =& batch_get(); // Initialize the batch if needed. if (empty($batch)) { $batch = array( 'sets' => array(), 'has_form_submits' => FALSE, ); } // Base and default properties for the batch set. // Use get_t() to allow batches during installation. $t = get_t(); $init = array( 'sandbox' => array(), 'results' => array(), 'success' => FALSE, 'start' => 0, 'elapsed' => 0, ); $defaults = array( 'title' => $t('Processing'), 'init_message' => $t('Initializing.'), 'progress_message' => $t('Completed @current of @total.'), 'error_message' => $t('An error has occurred.'), 'css' => array(), ); $batch_set = $init + $batch_definition + $defaults; // Tweak init_message to avoid the bottom of the page flickering down after // init phase. $batch_set['init_message'] .= '
       '; // The non-concurrent workflow of batch execution allows us to save // numberOfItems() queries by handling our own counter. $batch_set['total'] = count($batch_set['operations']); $batch_set['count'] = $batch_set['total']; // Add the set to the batch. if (empty($batch['id'])) { // The batch is not running yet. Simply add the new set. $batch['sets'][] = $batch_set; } else { // The set is being added while the batch is running. Insert the new set // right after the current one to ensure execution order, and store its // operations in a queue. $index = $batch['current_set'] + 1; $slice1 = array_slice($batch['sets'], 0, $index); $slice2 = array_slice($batch['sets'], $index); $batch['sets'] = array_merge($slice1, array($batch_set), $slice2); _batch_populate_queue($batch, $index); } } } /** * Processes the batch. * * Unless the batch has been marked with 'progressive' = FALSE, the function * issues a drupal_goto and thus ends page execution. * * This function is generally not needed in form submit handlers; * Form API takes care of batches that were set during form submission. * * @param $redirect * (optional) Path to redirect to when the batch has finished processing. * @param $url * (optional - should only be used for separate scripts like update.php) * URL of the batch processing page. * @param $redirect_callback * (optional) Specify a function to be called to redirect to the progressive * processing page. By default drupal_goto() will be used to redirect to a * page which will do the progressive page. Specifying another function will * allow the progressive processing to be processed differently. */ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') { $batch =& batch_get(); drupal_theme_initialize(); if (isset($batch)) { // Add process information $process_info = array( 'current_set' => 0, 'progressive' => TRUE, 'url' => $url, 'url_options' => array(), 'source_url' => $_GET['q'], 'redirect' => $redirect, 'theme' => $GLOBALS['theme_key'], 'redirect_callback' => $redirect_callback, ); $batch += $process_info; // The batch is now completely built. Allow other modules to make changes // to the batch so that it is easier to reuse batch processes in other // environments. drupal_alter('batch', $batch); // Assign an arbitrary id: don't rely on a serial column in the 'batch' // table, since non-progressive batches skip database storage completely. $batch['id'] = db_next_id(); // Move operations to a job queue. Non-progressive batches will use a // memory-based queue. foreach ($batch['sets'] as $key => $batch_set) { _batch_populate_queue($batch, $key); } // Initiate processing. if ($batch['progressive']) { // Now that we have a batch id, we can generate the redirection link in // the generic error message. $t = get_t(); $batch['error_message'] = $t('Please continue to the error page', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished'))))); // Clear the way for the drupal_goto() redirection to the batch processing // page, by saving and unsetting the 'destination', if there is any. if (isset($_GET['destination'])) { $batch['destination'] = $_GET['destination']; unset($_GET['destination']); } // Store the batch. db_insert('batch') ->fields(array( 'bid' => $batch['id'], 'timestamp' => REQUEST_TIME, 'token' => drupal_get_token($batch['id']), 'batch' => serialize($batch), )) ->execute(); // Set the batch number in the session to guarantee that it will stay alive. $_SESSION['batches'][$batch['id']] = TRUE; // Redirect for processing. $function = $batch['redirect_callback']; if (function_exists($function)) { $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id']))); } } else { // Non-progressive execution: bypass the whole progressbar workflow // and execute the batch in one pass. require_once DRUPAL_ROOT . '/includes/batch.inc'; _batch_process(); } } } /** * Retrieves the current batch. */ function &batch_get() { // Not drupal_static(), because Batch API operates at a lower level than most // use-cases for resetting static variables, and we specifically do not want a // global drupal_static_reset() resetting the batch information. Functions // that are part of the Batch API and need to reset the batch information may // call batch_get() and manipulate the result by reference. Functions that are // not part of the Batch API can also do this, but shouldn't. static $batch = array(); return $batch; } /** * Populates a job queue with the operations of a batch set. * * Depending on whether the batch is progressive or not, the BatchQueue or * BatchMemoryQueue handler classes will be used. * * @param $batch * The batch array. * @param $set_id * The id of the set to process. * * @return * The name and class of the queue are added by reference to the batch set. */ function _batch_populate_queue(&$batch, $set_id) { $batch_set = &$batch['sets'][$set_id]; if (isset($batch_set['operations'])) { $batch_set += array( 'queue' => array( 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id, 'class' => $batch['progressive'] ? 'BatchQueue' : 'BatchMemoryQueue', ), ); $queue = _batch_queue($batch_set); $queue->createQueue(); foreach ($batch_set['operations'] as $operation) { $queue->createItem($operation); } unset($batch_set['operations']); } } /** * Returns a queue object for a batch set. * * @param $batch_set * The batch set. * * @return * The queue object. */ function _batch_queue($batch_set) { static $queues; // The class autoloader is not available when running update.php, so make // sure the files are manually included. if (!isset($queues)) { $queues = array(); require_once DRUPAL_ROOT . '/modules/system/system.queue.inc'; require_once DRUPAL_ROOT . '/includes/batch.queue.inc'; } if (isset($batch_set['queue'])) { $name = $batch_set['queue']['name']; $class = $batch_set['queue']['class']; if (!isset($queues[$class][$name])) { $queues[$class][$name] = new $class($name); } return $queues[$class][$name]; } } /** * @} End of "defgroup batch". */ drupal-7.26/includes/entity.inc0000644001412200141220000013202212265562324016035 0ustar benderbender $value. * * @return * An array of entity objects indexed by their ids. When no results are * found, an empty array is returned. */ public function load($ids = array(), $conditions = array()); } /** * Default implementation of DrupalEntityControllerInterface. * * This class can be used as-is by most simple entity types. Entity types * requiring special handling can extend the class. */ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { /** * Static cache of entities. * * @var array */ protected $entityCache; /** * Entity type for this controller instance. * * @var string */ protected $entityType; /** * Array of information about the entity. * * @var array * * @see entity_get_info() */ protected $entityInfo; /** * Additional arguments to pass to hook_TYPE_load(). * * Set before calling DrupalDefaultEntityController::attachLoad(). * * @var array */ protected $hookLoadArguments; /** * Name of the entity's ID field in the entity database table. * * @var string */ protected $idKey; /** * Name of entity's revision database table field, if it supports revisions. * * Has the value FALSE if this entity does not use revisions. * * @var string */ protected $revisionKey; /** * The table that stores revisions, if the entity supports revisions. * * @var string */ protected $revisionTable; /** * Whether this entity type should use the static cache. * * Set by entity info. * * @var boolean */ protected $cache; /** * Constructor: sets basic variables. * * @param $entityType * The entity type for which the instance is created. */ public function __construct($entityType) { $this->entityType = $entityType; $this->entityInfo = entity_get_info($entityType); $this->entityCache = array(); $this->hookLoadArguments = array(); $this->idKey = $this->entityInfo['entity keys']['id']; // Check if the entity type supports revisions. if (!empty($this->entityInfo['entity keys']['revision'])) { $this->revisionKey = $this->entityInfo['entity keys']['revision']; $this->revisionTable = $this->entityInfo['revision table']; } else { $this->revisionKey = FALSE; } // Check if the entity type supports static caching of loaded entities. $this->cache = !empty($this->entityInfo['static cache']); } /** * Implements DrupalEntityControllerInterface::resetCache(). */ public function resetCache(array $ids = NULL) { if (isset($ids)) { foreach ($ids as $id) { unset($this->entityCache[$id]); } } else { $this->entityCache = array(); } } /** * Implements DrupalEntityControllerInterface::load(). */ public function load($ids = array(), $conditions = array()) { $entities = array(); // Revisions are not statically cached, and require a different query to // other conditions, so separate the revision id into its own variable. if ($this->revisionKey && isset($conditions[$this->revisionKey])) { $revision_id = $conditions[$this->revisionKey]; unset($conditions[$this->revisionKey]); } else { $revision_id = FALSE; } // Create a new variable which is either a prepared version of the $ids // array for later comparison with the entity cache, or FALSE if no $ids // were passed. The $ids array is reduced as items are loaded from cache, // and we need to know if it's empty for this reason to avoid querying the // database when all requested entities are loaded from cache. $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; // Try to load entities from the static cache, if the entity type supports // static caching. if ($this->cache && !$revision_id) { $entities += $this->cacheGet($ids, $conditions); // If any entities were loaded, remove them from the ids still to load. if ($passed_ids) { $ids = array_keys(array_diff_key($passed_ids, $entities)); } } // Load any remaining entities from the database. This is the case if $ids // is set to FALSE (so we load all entities), if there are any ids left to // load, if loading a revision, or if $conditions was passed without $ids. if ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids)) { // Build the query. $query = $this->buildQuery($ids, $conditions, $revision_id); $queried_entities = $query ->execute() ->fetchAllAssoc($this->idKey); } // Pass all entities loaded from the database through $this->attachLoad(), // which attaches fields (if supported by the entity type) and calls the // entity type specific load callback, for example hook_node_load(). if (!empty($queried_entities)) { $this->attachLoad($queried_entities, $revision_id); $entities += $queried_entities; } if ($this->cache) { // Add entities to the cache if we are not loading a revision. if (!empty($queried_entities) && !$revision_id) { $this->cacheSet($queried_entities); } } // Ensure that the returned array is ordered the same as the original // $ids array if this was passed in and remove any invalid ids. if ($passed_ids) { // Remove any invalid ids from the array. $passed_ids = array_intersect_key($passed_ids, $entities); foreach ($entities as $entity) { $passed_ids[$entity->{$this->idKey}] = $entity; } $entities = $passed_ids; } return $entities; } /** * Builds the query to load the entity. * * This has full revision support. For entities requiring special queries, * the class can be extended, and the default query can be constructed by * calling parent::buildQuery(). This is usually necessary when the object * being loaded needs to be augmented with additional data from another * table, such as loading node type into comments or vocabulary machine name * into terms, however it can also support $conditions on different tables. * See CommentController::buildQuery() or TaxonomyTermController::buildQuery() * for examples. * * @param $ids * An array of entity IDs, or FALSE to load all entities. * @param $conditions * An array of conditions in the form 'field' => $value. * @param $revision_id * The ID of the revision to load, or FALSE if this query is asking for the * most current revision(s). * * @return SelectQuery * A SelectQuery object for loading the entity. */ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = db_select($this->entityInfo['base table'], 'base'); $query->addTag($this->entityType . '_load_multiple'); if ($revision_id) { $query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $revision_id)); } elseif ($this->revisionKey) { $query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}"); } // Add fields from the {entity} table. $entity_fields = $this->entityInfo['schema_fields_sql']['base table']; if ($this->revisionKey) { // Add all fields from the {entity_revision} table. $entity_revision_fields = drupal_map_assoc($this->entityInfo['schema_fields_sql']['revision table']); // The id field is provided by entity, so remove it. unset($entity_revision_fields[$this->idKey]); // Remove all fields from the base table that are also fields by the same // name in the revision table. $entity_field_keys = array_flip($entity_fields); foreach ($entity_revision_fields as $key => $name) { if (isset($entity_field_keys[$name])) { unset($entity_fields[$entity_field_keys[$name]]); } } $query->fields('revision', $entity_revision_fields); } $query->fields('base', $entity_fields); if ($ids) { $query->condition("base.{$this->idKey}", $ids, 'IN'); } if ($conditions) { foreach ($conditions as $field => $value) { $query->condition('base.' . $field, $value); } } return $query; } /** * Attaches data to entities upon loading. * * This will attach fields, if the entity is fieldable. It calls * hook_entity_load() for modules which need to add data to all entities. * It also calls hook_TYPE_load() on the loaded entities. For example * hook_node_load() or hook_user_load(). If your hook_TYPE_load() * expects special parameters apart from the queried entities, you can set * $this->hookLoadArguments prior to calling the method. * See NodeController::attachLoad() for an example. * * @param $queried_entities * Associative array of query results, keyed on the entity ID. * @param $revision_id * ID of the revision that was loaded, or FALSE if the most current revision * was loaded. */ protected function attachLoad(&$queried_entities, $revision_id = FALSE) { // Attach fields. if ($this->entityInfo['fieldable']) { if ($revision_id) { field_attach_load_revision($this->entityType, $queried_entities); } else { field_attach_load($this->entityType, $queried_entities); } } // Call hook_entity_load(). foreach (module_implements('entity_load') as $module) { $function = $module . '_entity_load'; $function($queried_entities, $this->entityType); } // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are // always the queried entities, followed by additional arguments set in // $this->hookLoadArguments. $args = array_merge(array($queried_entities), $this->hookLoadArguments); foreach (module_implements($this->entityInfo['load hook']) as $module) { call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args); } } /** * Gets entities from the static cache. * * @param $ids * If not empty, return entities that match these IDs. * @param $conditions * If set, return entities that match all of these conditions. * * @return * Array of entities from the entity cache. */ protected function cacheGet($ids, $conditions = array()) { $entities = array(); // Load any available entities from the internal cache. if (!empty($this->entityCache)) { if ($ids) { $entities += array_intersect_key($this->entityCache, array_flip($ids)); } // If loading entities only by conditions, fetch all available entities // from the cache. Entities which don't match are removed later. elseif ($conditions) { $entities = $this->entityCache; } } // Exclude any entities loaded from cache if they don't match $conditions. // This ensures the same behavior whether loading from memory or database. if ($conditions) { foreach ($entities as $entity) { $entity_values = (array) $entity; if (array_diff_assoc($conditions, $entity_values)) { unset($entities[$entity->{$this->idKey}]); } } } return $entities; } /** * Stores entities in the static entity cache. * * @param $entities * Entities to store in the cache. */ protected function cacheSet($entities) { $this->entityCache += $entities; } } /** * Exception thrown by EntityFieldQuery() on unsupported query syntax. * * Some storage modules might not support the full range of the syntax for * conditions, and will raise an EntityFieldQueryException when an unsupported * condition was specified. */ class EntityFieldQueryException extends Exception {} /** * Retrieves entities matching a given set of conditions. * * This class allows finding entities based on entity properties (for example, * node->changed), field values, and generic entity meta data (bundle, * entity type, entity id, and revision ID). It is not possible to query across * multiple entity types. For example, there is no facility to find published * nodes written by users created in the last hour, as this would require * querying both node->status and user->created. * * Normally we would not want to have public properties on the object, as that * allows the object's state to become inconsistent too easily. However, this * class's standard use case involves primarily code that does need to have * direct access to the collected properties in order to handle alternate * execution routines. We therefore use public properties for simplicity. Note * that code that is simply creating and running a field query should still use * the appropriate methods to add conditions on the query. * * Storage engines are not required to support every type of query. By default, * an EntityFieldQueryException will be raised if an unsupported condition is * specified or if the query has field conditions or sorts that are stored in * different field storage engines. However, this logic can be overridden in * hook_entity_query_alter(). * * Also note that this query does not automatically respect entity access * restrictions. Node access control is performed by the SQL storage engine but * other storage engines might not do this. */ class EntityFieldQuery { /** * Indicates that both deleted and non-deleted fields should be returned. * * @see EntityFieldQuery::deleted() */ const RETURN_ALL = NULL; /** * TRUE if the query has already been altered, FALSE if it hasn't. * * Used in alter hooks to check for cloned queries that have already been * altered prior to the clone (for example, the pager count query). * * @var boolean */ public $altered = FALSE; /** * Associative array of entity-generic metadata conditions. * * @var array * * @see EntityFieldQuery::entityCondition() */ public $entityConditions = array(); /** * List of field conditions. * * @var array * * @see EntityFieldQuery::fieldCondition() */ public $fieldConditions = array(); /** * List of field meta conditions (language and delta). * * Field conditions operate on columns specified by hook_field_schema(), * the meta conditions operate on columns added by the system: delta * and language. These can not be mixed with the field conditions because * field columns can have any name including delta and language. * * @var array * * @see EntityFieldQuery::fieldLanguageCondition() * @see EntityFieldQuery::fieldDeltaCondition() */ public $fieldMetaConditions = array(); /** * List of property conditions. * * @var array * * @see EntityFieldQuery::propertyCondition() */ public $propertyConditions = array(); /** * List of order clauses. * * @var array */ public $order = array(); /** * The query range. * * @var array * * @see EntityFieldQuery::range() */ public $range = array(); /** * The query pager data. * * @var array * * @see EntityFieldQuery::pager() */ public $pager = array(); /** * Query behavior for deleted data. * * TRUE to return only deleted data, FALSE to return only non-deleted data, * EntityFieldQuery::RETURN_ALL to return everything. * * @see EntityFieldQuery::deleted() */ public $deleted = FALSE; /** * A list of field arrays used. * * Field names passed to EntityFieldQuery::fieldCondition() and * EntityFieldQuery::fieldOrderBy() are run through field_info_field() before * stored in this array. This way, the elements of this array are field * arrays. * * @var array */ public $fields = array(); /** * TRUE if this is a count query, FALSE if it isn't. * * @var boolean */ public $count = FALSE; /** * Flag indicating whether this is querying current or all revisions. * * @var int * * @see EntityFieldQuery::age() */ public $age = FIELD_LOAD_CURRENT; /** * A list of the tags added to this query. * * @var array * * @see EntityFieldQuery::addTag() */ public $tags = array(); /** * A list of metadata added to this query. * * @var array * * @see EntityFieldQuery::addMetaData() */ public $metaData = array(); /** * The ordered results. * * @var array * * @see EntityFieldQuery::execute(). */ public $orderedResults = array(); /** * The method executing the query, if it is overriding the default. * * @var string * * @see EntityFieldQuery::execute(). */ public $executeCallback = ''; /** * Adds a condition on entity-generic metadata. * * If the overall query contains only entity conditions or ordering, or if * there are property conditions, then specifying the entity type is * mandatory. If there are field conditions or ordering but no property * conditions or ordering, then specifying an entity type is optional. While * the field storage engine might support field conditions on more than one * entity type, there is no way to query across multiple entity base tables by * default. To specify the entity type, pass in 'entity_type' for $name, * the type as a string for $value, and no $operator (it's disregarded). * * 'bundle', 'revision_id' and 'entity_id' have no such restrictions. * * Note: The "comment" entity type does not support bundle conditions. * * @param $name * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. * @param $value * The value for $name. In most cases, this is a scalar. For more complex * options, it is an array. The meaning of each element in the array is * dependent on $operator. * @param $operator * Possible values: * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These * operators expect $value to be a literal of the same type as the * column. * - 'IN', 'NOT IN': These operators expect $value to be an array of * literals of the same type as the column. * - 'BETWEEN': This operator expects $value to be an array of two literals * of the same type as the column. * The operator can be omitted, and will default to 'IN' if the value is an * array, or to '=' otherwise. * * @return EntityFieldQuery * The called object. */ public function entityCondition($name, $value, $operator = NULL) { // The '!=' operator is deprecated in favour of the '<>' operator since the // latter is ANSI SQL compatible. if ($operator == '!=') { $operator = '<>'; } $this->entityConditions[$name] = array( 'value' => $value, 'operator' => $operator, ); return $this; } /** * Adds a condition on field values. * * Note that entities with empty field values will be excluded from the * EntityFieldQuery results when using this method. * * @param $field * Either a field name or a field array. * @param $column * The column that should hold the value to be matched. * @param $value * The value to test the column value against. * @param $operator * The operator to be used to test the given value. * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same * $delta_group. * @param $language_group * An arbitrary identifier: conditions in the same group must have the same * $language_group. * * @return EntityFieldQuery * The called object. * * @see EntityFieldQuery::addFieldCondition * @see EntityFieldQuery::deleted */ public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { return $this->addFieldCondition($this->fieldConditions, $field, $column, $value, $operator, $delta_group, $language_group); } /** * Adds a condition on the field language column. * * @param $field * Either a field name or a field array. * @param $value * The value to test the column value against. * @param $operator * The operator to be used to test the given value. * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same * $delta_group. * @param $language_group * An arbitrary identifier: conditions in the same group must have the same * $language_group. * * @return EntityFieldQuery * The called object. * * @see EntityFieldQuery::addFieldCondition * @see EntityFieldQuery::deleted */ public function fieldLanguageCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { return $this->addFieldCondition($this->fieldMetaConditions, $field, 'language', $value, $operator, $delta_group, $language_group); } /** * Adds a condition on the field delta column. * * @param $field * Either a field name or a field array. * @param $value * The value to test the column value against. * @param $operator * The operator to be used to test the given value. * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same * $delta_group. * @param $language_group * An arbitrary identifier: conditions in the same group must have the same * $language_group. * * @return EntityFieldQuery * The called object. * * @see EntityFieldQuery::addFieldCondition * @see EntityFieldQuery::deleted */ public function fieldDeltaCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { return $this->addFieldCondition($this->fieldMetaConditions, $field, 'delta', $value, $operator, $delta_group, $language_group); } /** * Adds the given condition to the proper condition array. * * @param $conditions * A reference to an array of conditions. * @param $field * Either a field name or a field array. * @param $column * A column defined in the hook_field_schema() of this field. If this is * omitted then the query will find only entities that have data in this * field, using the entity and property conditions if there are any. * @param $value * The value to test the column value against. In most cases, this is a * scalar. For more complex options, it is an array. The meaning of each * element in the array is dependent on $operator. * @param $operator * Possible values: * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These * operators expect $value to be a literal of the same type as the * column. * - 'IN', 'NOT IN': These operators expect $value to be an array of * literals of the same type as the column. * - 'BETWEEN': This operator expects $value to be an array of two literals * of the same type as the column. * The operator can be omitted, and will default to 'IN' if the value is an * array, or to '=' otherwise. * @param $delta_group * An arbitrary identifier: conditions in the same group must have the same * $delta_group. For example, let's presume a multivalue field which has * two columns, 'color' and 'shape', and for entity id 1, there are two * values: red/square and blue/circle. Entity ID 1 does not have values * corresponding to 'red circle', however if you pass 'red' and 'circle' as * conditions, it will appear in the results - by default queries will run * against any combination of deltas. By passing the conditions with the * same $delta_group it will ensure that only values attached to the same * delta are matched, and entity 1 would then be excluded from the results. * @param $language_group * An arbitrary identifier: conditions in the same group must have the same * $language_group. * * @return EntityFieldQuery * The called object. */ protected function addFieldCondition(&$conditions, $field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { // The '!=' operator is deprecated in favour of the '<>' operator since the // latter is ANSI SQL compatible. if ($operator == '!=') { $operator = '<>'; } if (is_scalar($field)) { $field_definition = field_info_field($field); if (empty($field_definition)) { throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field))); } $field = $field_definition; } // Ensure the same index is used for field conditions as for fields. $index = count($this->fields); $this->fields[$index] = $field; if (isset($column)) { $conditions[$index] = array( 'field' => $field, 'column' => $column, 'value' => $value, 'operator' => $operator, 'delta_group' => $delta_group, 'language_group' => $language_group, ); } return $this; } /** * Adds a condition on an entity-specific property. * * An $entity_type must be specified by calling * EntityFieldCondition::entityCondition('entity_type', $entity_type) before * executing the query. Also, by default only entities stored in SQL are * supported; however, EntityFieldQuery::executeCallback can be set to handle * different entity storage. * * @param $column * A column defined in the hook_schema() of the base table of the entity. * @param $value * The value to test the field against. In most cases, this is a scalar. For * more complex options, it is an array. The meaning of each element in the * array is dependent on $operator. * @param $operator * Possible values: * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These * operators expect $value to be a literal of the same type as the * column. * - 'IN', 'NOT IN': These operators expect $value to be an array of * literals of the same type as the column. * - 'BETWEEN': This operator expects $value to be an array of two literals * of the same type as the column. * The operator can be omitted, and will default to 'IN' if the value is an * array, or to '=' otherwise. * * @return EntityFieldQuery * The called object. */ public function propertyCondition($column, $value, $operator = NULL) { // The '!=' operator is deprecated in favour of the '<>' operator since the // latter is ANSI SQL compatible. if ($operator == '!=') { $operator = '<>'; } $this->propertyConditions[] = array( 'column' => $column, 'value' => $value, 'operator' => $operator, ); return $this; } /** * Orders the result set by entity-generic metadata. * * If called multiple times, the query will order by each specified column in * the order this method is called. * * Note: The "comment" and "taxonomy_term" entity types don't support ordering * by bundle. For "taxonomy_term", propertyOrderBy('vid') can be used instead. * * @param $name * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. * @param $direction * The direction to sort. Legal values are "ASC" and "DESC". * * @return EntityFieldQuery * The called object. */ public function entityOrderBy($name, $direction = 'ASC') { $this->order[] = array( 'type' => 'entity', 'specifier' => $name, 'direction' => $direction, ); return $this; } /** * Orders the result set by a given field column. * * If called multiple times, the query will order by each specified column in * the order this method is called. Note that entities with empty field * values will be excluded from the EntityFieldQuery results when using this * method. * * @param $field * Either a field name or a field array. * @param $column * A column defined in the hook_field_schema() of this field. entity_id and * bundle can also be used. * @param $direction * The direction to sort. Legal values are "ASC" and "DESC". * * @return EntityFieldQuery * The called object. */ public function fieldOrderBy($field, $column, $direction = 'ASC') { if (is_scalar($field)) { $field_definition = field_info_field($field); if (empty($field_definition)) { throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field))); } $field = $field_definition; } // Save the index used for the new field, for later use in field storage. $index = count($this->fields); $this->fields[$index] = $field; $this->order[] = array( 'type' => 'field', 'specifier' => array( 'field' => $field, 'index' => $index, 'column' => $column, ), 'direction' => $direction, ); return $this; } /** * Orders the result set by an entity-specific property. * * An $entity_type must be specified by calling * EntityFieldCondition::entityCondition('entity_type', $entity_type) before * executing the query. * * If called multiple times, the query will order by each specified column in * the order this method is called. * * @param $column * The column on which to order. * @param $direction * The direction to sort. Legal values are "ASC" and "DESC". * * @return EntityFieldQuery * The called object. */ public function propertyOrderBy($column, $direction = 'ASC') { $this->order[] = array( 'type' => 'property', 'specifier' => $column, 'direction' => $direction, ); return $this; } /** * Sets the query to be a count query only. * * @return EntityFieldQuery * The called object. */ public function count() { $this->count = TRUE; return $this; } /** * Restricts a query to a given range in the result set. * * @param $start * The first entity from the result set to return. If NULL, removes any * range directives that are set. * @param $length * The number of entities to return from the result set. * * @return EntityFieldQuery * The called object. */ public function range($start = NULL, $length = NULL) { $this->range = array( 'start' => $start, 'length' => $length, ); return $this; } /** * Enables a pager for the query. * * @param $limit * An integer specifying the number of elements per page. If passed a false * value (FALSE, 0, NULL), the pager is disabled. * @param $element * An optional integer to distinguish between multiple pagers on one page. * If not provided, one is automatically calculated. * * @return EntityFieldQuery * The called object. */ public function pager($limit = 10, $element = NULL) { if (!isset($element)) { $element = PagerDefault::$maxElement++; } elseif ($element >= PagerDefault::$maxElement) { PagerDefault::$maxElement = $element + 1; } $this->pager = array( 'limit' => $limit, 'element' => $element, ); return $this; } /** * Enables sortable tables for this query. * * @param $headers * An EFQ Header array based on which the order clause is added to the * query. * * @return EntityFieldQuery * The called object. */ public function tableSort(&$headers) { // If 'field' is not initialized, the header columns aren't clickable foreach ($headers as $key =>$header) { if (is_array($header) && isset($header['specifier'])) { $headers[$key]['field'] = ''; } } $order = tablesort_get_order($headers); $direction = tablesort_get_sort($headers); foreach ($headers as $header) { if (is_array($header) && ($header['data'] == $order['name'])) { if ($header['type'] == 'field') { $this->fieldOrderBy($header['specifier']['field'], $header['specifier']['column'], $direction); } else { $header['direction'] = $direction; $this->order[] = $header; } } } return $this; } /** * Filters on the data being deleted. * * @param $deleted * TRUE to only return deleted data, FALSE to return non-deleted data, * EntityFieldQuery::RETURN_ALL to return everything. Defaults to FALSE. * * @return EntityFieldQuery * The called object. */ public function deleted($deleted = TRUE) { $this->deleted = $deleted; return $this; } /** * Queries the current or every revision. * * Note that this only affects field conditions. Property conditions always * apply to the current revision. * @TODO: Once revision tables have been cleaned up, revisit this. * * @param $age * - FIELD_LOAD_CURRENT (default): Query the most recent revisions for all * entities. The results will be keyed by entity type and entity ID. * - FIELD_LOAD_REVISION: Query all revisions. The results will be keyed by * entity type and entity revision ID. * * @return EntityFieldQuery * The called object. */ public function age($age) { $this->age = $age; return $this; } /** * Adds a tag to the query. * * Tags are strings that mark a query so that hook_query_alter() and * hook_query_TAG_alter() implementations may decide if they wish to alter * the query. A query may have any number of tags, and they must be valid PHP * identifiers (composed of letters, numbers, and underscores). For example, * queries involving nodes that will be displayed for a user need to add the * tag 'node_access', so that the node module can add access restrictions to * the query. * * If an entity field query has tags, it must also have an entity type * specified, because the alter hook will need the entity base table. * * @param string $tag * The tag to add. * * @return EntityFieldQuery * The called object. */ public function addTag($tag) { $this->tags[$tag] = $tag; return $this; } /** * Adds additional metadata to the query. * * Sometimes a query may need to provide additional contextual data for the * alter hook. The alter hook implementations may then use that information * to decide if and how to take action. * * @param $key * The unique identifier for this piece of metadata. Must be a string that * follows the same rules as any other PHP identifier. * @param $object * The additional data to add to the query. May be any valid PHP variable. * * @return EntityFieldQuery * The called object. */ public function addMetaData($key, $object) { $this->metaData[$key] = $object; return $this; } /** * Executes the query. * * After executing the query, $this->orderedResults will contain a list of * the same stub entities in the order returned by the query. This is only * relevant if there are multiple entity types in the returned value and * a field ordering was requested. In every other case, the returned value * contains everything necessary for processing. * * @return * Either a number if count() was called or an array of associative arrays * of stub entities. The outer array keys are entity types, and the inner * array keys are the relevant ID. (In most cases this will be the entity * ID. The only exception is when age=FIELD_LOAD_REVISION is used and field * conditions or sorts are present -- in this case, the key will be the * revision ID.) The entity type will only exist in the outer array if * results were found. The inner array values are always stub entities, as * returned by entity_create_stub_entity(). To traverse the returned array: * @code * foreach ($query->execute() as $entity_type => $entities) { * foreach ($entities as $entity_id => $entity) { * @endcode * Note if the entity type is known, then the following snippet will load * the entities found: * @code * $result = $query->execute(); * if (!empty($result[$my_type])) { * $entities = entity_load($my_type, array_keys($result[$my_type])); * } * @endcode */ public function execute() { // Give a chance to other modules to alter the query. drupal_alter('entity_query', $this); $this->altered = TRUE; // Initialize the pager. $this->initializePager(); // Execute the query using the correct callback. $result = call_user_func($this->queryCallback(), $this); return $result; } /** * Determines the query callback to use for this entity query. * * @return * A callback that can be used with call_user_func(). */ public function queryCallback() { // Use the override from $this->executeCallback. It can be set either // while building the query, or using hook_entity_query_alter(). if (function_exists($this->executeCallback)) { return $this->executeCallback; } // If there are no field conditions and sorts, and no execute callback // then we default to querying entity tables in SQL. if (empty($this->fields)) { return array($this, 'propertyQuery'); } // If no override, find the storage engine to be used. foreach ($this->fields as $field) { if (!isset($storage)) { $storage = $field['storage']['module']; } elseif ($storage != $field['storage']['module']) { throw new EntityFieldQueryException(t("Can't handle more than one field storage engine")); } } if ($storage) { // Use hook_field_storage_query() from the field storage. return $storage . '_field_storage_query'; } else { throw new EntityFieldQueryException(t("Field storage engine not found.")); } } /** * Queries entity tables in SQL for property conditions and sorts. * * This method is only used if there are no field conditions and sorts. * * @return * See EntityFieldQuery::execute(). */ protected function propertyQuery() { if (empty($this->entityConditions['entity_type'])) { throw new EntityFieldQueryException(t('For this query an entity type must be specified.')); } $entity_type = $this->entityConditions['entity_type']['value']; $entity_info = entity_get_info($entity_type); if (empty($entity_info['base table'])) { throw new EntityFieldQueryException(t('Entity %entity has no base table.', array('%entity' => $entity_type))); } $base_table = $entity_info['base table']; $base_table_schema = drupal_get_schema($base_table); $select_query = db_select($base_table); $select_query->addExpression(':entity_type', 'entity_type', array(':entity_type' => $entity_type)); // Process the property conditions. foreach ($this->propertyConditions as $property_condition) { $this->addCondition($select_query, $base_table . '.' . $property_condition['column'], $property_condition); } // Process the four possible entity condition. // The id field is always present in entity keys. $sql_field = $entity_info['entity keys']['id']; $id_map['entity_id'] = $sql_field; $select_query->addField($base_table, $sql_field, 'entity_id'); if (isset($this->entityConditions['entity_id'])) { $this->addCondition($select_query, $base_table . '.' . $sql_field, $this->entityConditions['entity_id']); } // If there is a revision key defined, use it. if (!empty($entity_info['entity keys']['revision'])) { $sql_field = $entity_info['entity keys']['revision']; $select_query->addField($base_table, $sql_field, 'revision_id'); if (isset($this->entityConditions['revision_id'])) { $this->addCondition($select_query, $base_table . '.' . $sql_field, $this->entityConditions['revision_id']); } } else { $sql_field = 'revision_id'; $select_query->addExpression('NULL', 'revision_id'); } $id_map['revision_id'] = $sql_field; // Handle bundles. if (!empty($entity_info['entity keys']['bundle'])) { $sql_field = $entity_info['entity keys']['bundle']; $having = FALSE; if (!empty($base_table_schema['fields'][$sql_field])) { $select_query->addField($base_table, $sql_field, 'bundle'); } } else { $sql_field = 'bundle'; $select_query->addExpression(':bundle', 'bundle', array(':bundle' => $entity_type)); $having = TRUE; } $id_map['bundle'] = $sql_field; if (isset($this->entityConditions['bundle'])) { if (!empty($entity_info['entity keys']['bundle'])) { $this->addCondition($select_query, $base_table . '.' . $sql_field, $this->entityConditions['bundle'], $having); } else { // This entity has no bundle, so invalidate the query. $select_query->where('1 = 0'); } } // Order the query. foreach ($this->order as $order) { if ($order['type'] == 'entity') { $key = $order['specifier']; if (!isset($id_map[$key])) { throw new EntityFieldQueryException(t('Do not know how to order on @key for @entity_type', array('@key' => $key, '@entity_type' => $entity_type))); } $select_query->orderBy($id_map[$key], $order['direction']); } elseif ($order['type'] == 'property') { $select_query->orderBy($base_table . '.' . $order['specifier'], $order['direction']); } } return $this->finishQuery($select_query); } /** * Gets the total number of results and initializes a pager for the query. * * The pager can be disabled by either setting the pager limit to 0, or by * setting this query to be a count query. */ function initializePager() { if ($this->pager && !empty($this->pager['limit']) && !$this->count) { $page = pager_find_page($this->pager['element']); $count_query = clone $this; $this->pager['total'] = $count_query->count()->execute(); $this->pager['start'] = $page * $this->pager['limit']; pager_default_initialize($this->pager['total'], $this->pager['limit'], $this->pager['element']); $this->range($this->pager['start'], $this->pager['limit']); } } /** * Finishes the query. * * Adds tags, metaData, range and returns the requested list or count. * * @param SelectQuery $select_query * A SelectQuery which has entity_type, entity_id, revision_id and bundle * fields added. * @param $id_key * Which field's values to use as the returned array keys. * * @return * See EntityFieldQuery::execute(). */ function finishQuery($select_query, $id_key = 'entity_id') { foreach ($this->tags as $tag) { $select_query->addTag($tag); } foreach ($this->metaData as $key => $object) { $select_query->addMetaData($key, $object); } $select_query->addMetaData('entity_field_query', $this); if ($this->range) { $select_query->range($this->range['start'], $this->range['length']); } if ($this->count) { return $select_query->countQuery()->execute()->fetchField(); } $return = array(); foreach ($select_query->execute() as $partial_entity) { $bundle = isset($partial_entity->bundle) ? $partial_entity->bundle : NULL; $entity = entity_create_stub_entity($partial_entity->entity_type, array($partial_entity->entity_id, $partial_entity->revision_id, $bundle)); $return[$partial_entity->entity_type][$partial_entity->$id_key] = $entity; $this->ordered_results[] = $partial_entity; } return $return; } /** * Adds a condition to an already built SelectQuery (internal function). * * This is a helper for hook_entity_query() and hook_field_storage_query(). * * @param SelectQuery $select_query * A SelectQuery object. * @param $sql_field * The name of the field. * @param $condition * A condition as described in EntityFieldQuery::fieldCondition() and * EntityFieldQuery::entityCondition(). * @param $having * HAVING or WHERE. This is necessary because SQL can't handle WHERE * conditions on aliased columns. */ public function addCondition(SelectQuery $select_query, $sql_field, $condition, $having = FALSE) { $method = $having ? 'havingCondition' : 'condition'; $like_prefix = ''; switch ($condition['operator']) { case 'CONTAINS': $like_prefix = '%'; case 'STARTS_WITH': $select_query->$method($sql_field, $like_prefix . db_like($condition['value']) . '%', 'LIKE'); break; default: $select_query->$method($sql_field, $condition['value'], $condition['operator']); } } } /** * Defines an exception thrown when a malformed entity is passed. */ class EntityMalformedException extends Exception { } drupal-7.26/includes/actions.inc0000644001412200141220000003277012265562324016172 0ustar benderbender variable_get('actions_max_stack', 35)) { watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR); return; } $actions = array(); $available_actions = actions_list(); $actions_result = array(); if (is_array($action_ids)) { $conditions = array(); foreach ($action_ids as $action_id) { if (is_numeric($action_id)) { $conditions[] = $action_id; } elseif (isset($available_actions[$action_id])) { $actions[$action_id] = $available_actions[$action_id]; } } // When we have action instances we must go to the database to retrieve // instance data. if (!empty($conditions)) { $query = db_select('actions'); $query->addField('actions', 'aid'); $query->addField('actions', 'type'); $query->addField('actions', 'callback'); $query->addField('actions', 'parameters'); $query->condition('aid', $conditions, 'IN'); $result = $query->execute(); foreach ($result as $action) { $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array(); $actions[$action->aid]['callback'] = $action->callback; $actions[$action->aid]['type'] = $action->type; } } // Fire actions, in no particular order. foreach ($actions as $action_id => $params) { // Configurable actions need parameters. if (is_numeric($action_id)) { $function = $params['callback']; if (function_exists($function)) { $context = array_merge($context, $params); $actions_result[$action_id] = $function($object, $context, $a1, $a2); } else { $actions_result[$action_id] = FALSE; } } // Singleton action; $action_id is the function name. else { $actions_result[$action_id] = $action_id($object, $context, $a1, $a2); } } } // Optimized execution of a single action. else { // If it's a configurable action, retrieve stored parameters. if (is_numeric($action_ids)) { $action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject(); $function = $action->callback; if (function_exists($function)) { $context = array_merge($context, unserialize($action->parameters)); $actions_result[$action_ids] = $function($object, $context, $a1, $a2); } else { $actions_result[$action_ids] = FALSE; } } // Singleton action; $action_ids is the function name. else { if (function_exists($action_ids)) { $actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2); } else { // Set to avoid undefined index error messages later. $actions_result[$action_ids] = FALSE; } } } $stack--; return $actions_result; } /** * Discovers all available actions by invoking hook_action_info(). * * This function contrasts with actions_get_all_actions(); see the * documentation of actions_get_all_actions() for an explanation. * * @param $reset * Reset the action info static cache. * * @return * An associative array keyed on action function name, with the same format * as the return value of hook_action_info(), containing all * modules' hook_action_info() return values as modified by any * hook_action_info_alter() implementations. * * @see hook_action_info() */ function actions_list($reset = FALSE) { $actions = &drupal_static(__FUNCTION__); if (!isset($actions) || $reset) { $actions = module_invoke_all('action_info'); drupal_alter('action_info', $actions); } // See module_implements() for an explanation of this cast. return (array) $actions; } /** * Retrieves all action instances from the database. * * This function differs from the actions_list() function, which gathers * actions by invoking hook_action_info(). The actions returned by this * function and the actions returned by actions_list() are partially * synchronized. Non-configurable actions from hook_action_info() * implementations are put into the database when actions_synchronize() is * called, which happens when admin/config/system/actions is visited. * Configurable actions are not added to the database until they are configured * in the user interface, in which case a database row is created for each * configuration of each action. * * @return * Associative array keyed by numeric action ID. Each value is an associative * array with keys 'callback', 'label', 'type' and 'configurable'. */ function actions_get_all_actions() { $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC); foreach ($actions as &$action) { $action['configurable'] = (bool) $action['parameters']; unset($action['parameters']); unset($action['aid']); } return $actions; } /** * Creates an associative array keyed by hashes of function names or IDs. * * Hashes are used to prevent actual function names from going out into HTML * forms and coming back. * * @param $actions * An associative array with function names or action IDs as keys * and associative arrays with keys 'label', 'type', etc. as values. * This is usually the output of actions_list() or actions_get_all_actions(). * * @return * An associative array whose keys are hashes of the input array keys, and * whose corresponding values are associative arrays with components * 'callback', 'label', 'type', and 'configurable' from the input array. */ function actions_actions_map($actions) { $actions_map = array(); foreach ($actions as $callback => $array) { $key = drupal_hash_base64($callback); $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback; $actions_map[$key]['label'] = $array['label']; $actions_map[$key]['type'] = $array['type']; $actions_map[$key]['configurable'] = $array['configurable']; } return $actions_map; } /** * Returns an action array key (function or ID), given its hash. * * Faster than actions_actions_map() when you only need the function name or ID. * * @param $hash * Hash of a function name or action ID array key. The array key * is a key into the return value of actions_list() (array key is the action * function name) or actions_get_all_actions() (array key is the action ID). * * @return * The corresponding array key, or FALSE if no match is found. */ function actions_function_lookup($hash) { // Check for a function name match. $actions_list = actions_list(); foreach ($actions_list as $function => $array) { if (drupal_hash_base64($function) == $hash) { return $function; } } $aid = FALSE; // Must be a configurable action; check database. $result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC); foreach ($result as $row) { if (drupal_hash_base64($row['aid']) == $hash) { $aid = $row['aid']; break; } } return $aid; } /** * Synchronizes actions that are provided by modules in hook_action_info(). * * Actions provided by modules in hook_action_info() implementations are * synchronized with actions that are stored in the actions database table. * This is necessary so that actions that do not require configuration can * receive action IDs. * * @param $delete_orphans * If TRUE, any actions that exist in the database but are no longer * found in the code (for example, because the module that provides them has * been disabled) will be deleted. */ function actions_synchronize($delete_orphans = FALSE) { $actions_in_code = actions_list(TRUE); $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC); // Go through all the actions provided by modules. foreach ($actions_in_code as $callback => $array) { // Ignore configurable actions since their instances get put in when the // user adds the action. if (!$array['configurable']) { // If we already have an action ID for this action, no need to assign aid. if (isset($actions_in_db[$callback])) { unset($actions_in_db[$callback]); } else { // This is a new singleton that we don't have an aid for; assign one. db_insert('actions') ->fields(array( 'aid' => $callback, 'type' => $array['type'], 'callback' => $callback, 'parameters' => '', 'label' => $array['label'], )) ->execute(); watchdog('actions', "Action '%action' added.", array('%action' => $array['label'])); } } } // Any actions that we have left in $actions_in_db are orphaned. if ($actions_in_db) { $orphaned = array_keys($actions_in_db); if ($delete_orphans) { $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll(); foreach ($actions as $action) { actions_delete($action->aid); watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => $action->label)); } } else { $link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan'); $count = count($actions_in_db); $orphans = implode(', ', $orphaned); watchdog('actions', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_INFO); } } } /** * Saves an action and its user-supplied parameter values to the database. * * @param $function * The name of the function to be called when this action is performed. * @param $type * The type of action, to describe grouping and/or context, e.g., 'node', * 'user', 'comment', or 'system'. * @param $params * An associative array with parameter names as keys and parameter values as * values. * @param $label * A user-supplied label of this particular action, e.g., 'Send e-mail * to Jim'. * @param $aid * The ID of this action. If omitted, a new action is created. * * @return * The ID of the action. */ function actions_save($function, $type, $params, $label, $aid = NULL) { // aid is the callback for singleton actions so we need to keep a separate // table for numeric aids. if (!$aid) { $aid = db_next_id(); } db_merge('actions') ->key(array('aid' => $aid)) ->fields(array( 'callback' => $function, 'type' => $type, 'parameters' => serialize($params), 'label' => $label, )) ->execute(); watchdog('actions', 'Action %action saved.', array('%action' => $label)); return $aid; } /** * Retrieves a single action from the database. * * @param $aid * The ID of the action to retrieve. * * @return * The appropriate action row from the database as an object. */ function actions_load($aid) { return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject(); } /** * Deletes a single action from the database. * * @param $aid * The ID of the action to delete. */ function actions_delete($aid) { db_delete('actions') ->condition('aid', $aid) ->execute(); module_invoke_all('actions_delete', $aid); } drupal-7.26/includes/module.inc0000644001412200141220000011776512265562324016027 0ustar benderbender $module) { drupal_get_filename('module', $name, $module['filename']); $list[$name] = $name; } } else { if ($refresh) { // For the $refresh case, make sure that system_list() returns fresh // data. drupal_static_reset('system_list'); } if ($bootstrap_refresh) { $list = system_list('bootstrap'); } else { // Not using drupal_map_assoc() here as that requires common.inc. $list = array_keys(system_list('module_enabled')); $list = (!empty($list) ? array_combine($list, $list) : array()); } } } if ($sort) { if (!isset($sorted_list)) { $sorted_list = $list; ksort($sorted_list); } return $sorted_list; } return $list; } /** * Builds a list of bootstrap modules and enabled modules and themes. * * @param $type * The type of list to return: * - module_enabled: All enabled modules. * - bootstrap: All enabled modules required for bootstrap. * - theme: All themes. * * @return * An associative array of modules or themes, keyed by name. For $type * 'bootstrap', the array values equal the keys. For $type 'module_enabled' * or 'theme', the array values are objects representing the respective * database row, with the 'info' property already unserialized. * * @see module_list() * @see list_themes() */ function system_list($type) { $lists = &drupal_static(__FUNCTION__); // For bootstrap modules, attempt to fetch the list from cache if possible. // if not fetch only the required information to fire bootstrap hooks // in case we are going to serve the page from cache. if ($type == 'bootstrap') { if (isset($lists['bootstrap'])) { return $lists['bootstrap']; } if ($cached = cache_get('bootstrap_modules', 'cache_bootstrap')) { $bootstrap_list = $cached->data; } else { $bootstrap_list = db_query("SELECT name, filename FROM {system} WHERE status = 1 AND bootstrap = 1 AND type = 'module' ORDER BY weight ASC, name ASC")->fetchAllAssoc('name'); cache_set('bootstrap_modules', $bootstrap_list, 'cache_bootstrap'); } // To avoid a separate database lookup for the filepath, prime the // drupal_get_filename() static cache for bootstrap modules only. // The rest is stored separately to keep the bootstrap module cache small. foreach ($bootstrap_list as $module) { drupal_get_filename('module', $module->name, $module->filename); } // We only return the module names here since module_list() doesn't need // the filename itself. $lists['bootstrap'] = array_keys($bootstrap_list); } // Otherwise build the list for enabled modules and themes. elseif (!isset($lists['module_enabled'])) { if ($cached = cache_get('system_list', 'cache_bootstrap')) { $lists = $cached->data; } else { $lists = array( 'module_enabled' => array(), 'theme' => array(), 'filepaths' => array(), ); // The module name (rather than the filename) is used as the fallback // weighting in order to guarantee consistent behavior across different // Drupal installations, which might have modules installed in different // locations in the file system. The ordering here must also be // consistent with the one used in module_implements(). $result = db_query("SELECT * FROM {system} WHERE type = 'theme' OR (type = 'module' AND status = 1) ORDER BY weight ASC, name ASC"); foreach ($result as $record) { $record->info = unserialize($record->info); // Build a list of all enabled modules. if ($record->type == 'module') { $lists['module_enabled'][$record->name] = $record; } // Build a list of themes. if ($record->type == 'theme') { $lists['theme'][$record->name] = $record; } // Build a list of filenames so drupal_get_filename can use it. if ($record->status) { $lists['filepaths'][] = array('type' => $record->type, 'name' => $record->name, 'filepath' => $record->filename); } } foreach ($lists['theme'] as $key => $theme) { if (!empty($theme->info['base theme'])) { // Make a list of the theme's base themes. require_once DRUPAL_ROOT . '/includes/theme.inc'; $lists['theme'][$key]->base_themes = drupal_find_base_themes($lists['theme'], $key); // Don't proceed if there was a problem with the root base theme. if (!current($lists['theme'][$key]->base_themes)) { continue; } // Determine the root base theme. $base_key = key($lists['theme'][$key]->base_themes); // Add to the list of sub-themes for each of the theme's base themes. foreach (array_keys($lists['theme'][$key]->base_themes) as $base_theme) { $lists['theme'][$base_theme]->sub_themes[$key] = $lists['theme'][$key]->info['name']; } // Add the base theme's theme engine info. $lists['theme'][$key]->info['engine'] = isset($lists['theme'][$base_key]->info['engine']) ? $lists['theme'][$base_key]->info['engine'] : 'theme'; } else { // A plain theme is its own engine. $base_key = $key; if (!isset($lists['theme'][$key]->info['engine'])) { $lists['theme'][$key]->info['engine'] = 'theme'; } } // Set the theme engine prefix. $lists['theme'][$key]->prefix = ($lists['theme'][$key]->info['engine'] == 'theme') ? $base_key : $lists['theme'][$key]->info['engine']; } cache_set('system_list', $lists, 'cache_bootstrap'); } // To avoid a separate database lookup for the filepath, prime the // drupal_get_filename() static cache with all enabled modules and themes. foreach ($lists['filepaths'] as $item) { drupal_get_filename($item['type'], $item['name'], $item['filepath']); } } return $lists[$type]; } /** * Resets all system_list() caches. */ function system_list_reset() { drupal_static_reset('system_list'); drupal_static_reset('system_rebuild_module_data'); drupal_static_reset('list_themes'); cache_clear_all('bootstrap_modules', 'cache_bootstrap'); cache_clear_all('system_list', 'cache_bootstrap'); } /** * Determines which modules require and are required by each module. * * @param $files * The array of filesystem objects used to rebuild the cache. * * @return * The same array with the new keys for each module: * - requires: An array with the keys being the modules that this module * requires. * - required_by: An array with the keys being the modules that will not work * without this module. */ function _module_build_dependencies($files) { require_once DRUPAL_ROOT . '/includes/graph.inc'; foreach ($files as $filename => $file) { $graph[$file->name]['edges'] = array(); if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) { foreach ($file->info['dependencies'] as $dependency) { $dependency_data = drupal_parse_dependency($dependency); $graph[$file->name]['edges'][$dependency_data['name']] = $dependency_data; } } } drupal_depth_first_search($graph); foreach ($graph as $module => $data) { $files[$module]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array(); $files[$module]->requires = isset($data['paths']) ? $data['paths'] : array(); $files[$module]->sort = $data['weight']; } return $files; } /** * Determines whether a given module exists. * * @param $module * The name of the module (without the .module extension). * * @return * TRUE if the module is both installed and enabled. */ function module_exists($module) { $list = module_list(); return isset($list[$module]); } /** * Loads a module's installation hooks. * * @param $module * The name of the module (without the .module extension). * * @return * The name of the module's install file, if successful; FALSE otherwise. */ function module_load_install($module) { // Make sure the installation API is available include_once DRUPAL_ROOT . '/includes/install.inc'; return module_load_include('install', $module); } /** * Loads a module include file. * * Examples: * @code * // Load node.admin.inc from the node module. * module_load_include('inc', 'node', 'node.admin'); * // Load content_types.inc from the node module. * module_load_include('inc', 'node', 'content_types'); * @endcode * * Do not use this function to load an install file, use module_load_install() * instead. Do not use this function in a global context since it requires * Drupal to be fully bootstrapped, use require_once DRUPAL_ROOT . '/path/file' * instead. * * @param $type * The include file's type (file extension). * @param $module * The module to which the include file belongs. * @param $name * (optional) The base file name (without the $type extension). If omitted, * $module is used; i.e., resulting in "$module.$type" by default. * * @return * The name of the included file, if successful; FALSE otherwise. */ function module_load_include($type, $module, $name = NULL) { if (!isset($name)) { $name = $module; } if (function_exists('drupal_get_path')) { $file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$name.$type"; if (is_file($file)) { require_once $file; return $file; } } return FALSE; } /** * Loads an include file for each module enabled in the {system} table. */ function module_load_all_includes($type, $name = NULL) { $modules = module_list(); foreach ($modules as $module) { module_load_include($type, $module, $name); } } /** * Enables or installs a given list of modules. * * Definitions: * - "Enabling" is the process of activating a module for use by Drupal. * - "Disabling" is the process of deactivating a module. * - "Installing" is the process of enabling it for the first time or after it * has been uninstalled. * - "Uninstalling" is the process of removing all traces of a module. * * Order of events: * - Gather and add module dependencies to $module_list (if applicable). * - For each module that is being enabled: * - Install module schema and update system registries and caches. * - If the module is being enabled for the first time or had been * uninstalled, invoke hook_install() and add it to the list of installed * modules. * - Invoke hook_enable(). * - Invoke hook_modules_installed(). * - Invoke hook_modules_enabled(). * * @param $module_list * An array of module names. * @param $enable_dependencies * If TRUE, dependencies will automatically be added and enabled in the * correct order. This incurs a significant performance cost, so use FALSE * if you know $module_list is already complete and in the correct order. * * @return * FALSE if one or more dependencies are missing, TRUE otherwise. * * @see hook_install() * @see hook_enable() * @see hook_modules_installed() * @see hook_modules_enabled() */ function module_enable($module_list, $enable_dependencies = TRUE) { if ($enable_dependencies) { // Get all module data so we can find dependencies and sort. $module_data = system_rebuild_module_data(); // Create an associative array with weights as values. $module_list = array_flip(array_values($module_list)); while (list($module) = each($module_list)) { if (!isset($module_data[$module])) { // This module is not found in the filesystem, abort. return FALSE; } if ($module_data[$module]->status) { // Skip already enabled modules. unset($module_list[$module]); continue; } $module_list[$module] = $module_data[$module]->sort; // Add dependencies to the list, with a placeholder weight. // The new modules will be processed as the while loop continues. foreach (array_keys($module_data[$module]->requires) as $dependency) { if (!isset($module_list[$dependency])) { $module_list[$dependency] = 0; } } } if (!$module_list) { // Nothing to do. All modules already enabled. return TRUE; } // Sort the module list by pre-calculated weights. arsort($module_list); $module_list = array_keys($module_list); } // Required for module installation checks. include_once DRUPAL_ROOT . '/includes/install.inc'; $modules_installed = array(); $modules_enabled = array(); foreach ($module_list as $module) { // Only process modules that are not already enabled. $existing = db_query("SELECT status FROM {system} WHERE type = :type AND name = :name", array( ':type' => 'module', ':name' => $module)) ->fetchObject(); if ($existing->status == 0) { // Load the module's code. drupal_load('module', $module); module_load_install($module); // Update the database and module list to reflect the new module. This // needs to be done first so that the module's hook implementations, // hook_schema() in particular, can be called while it is being // installed. db_update('system') ->fields(array('status' => 1)) ->condition('type', 'module') ->condition('name', $module) ->execute(); // Refresh the module list to include it. system_list_reset(); module_list(TRUE); module_implements('', FALSE, TRUE); _system_update_bootstrap_status(); // Update the registry to include it. registry_update(); // Refresh the schema to include it. drupal_get_schema(NULL, TRUE); // Update the theme registry to include it. drupal_theme_rebuild(); // Clear entity cache. entity_info_cache_clear(); // Now install the module if necessary. if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { drupal_install_schema($module); // Set the schema version to the number of the last update provided // by the module. $versions = drupal_get_schema_versions($module); $version = $versions ? max($versions) : SCHEMA_INSTALLED; // If the module has no current updates, but has some that were // previously removed, set the version to the value of // hook_update_last_removed(). if ($last_removed = module_invoke($module, 'update_last_removed')) { $version = max($version, $last_removed); } drupal_set_installed_schema_version($module, $version); // Allow the module to perform install tasks. module_invoke($module, 'install'); // Record the fact that it was installed. $modules_installed[] = $module; watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO); } // Enable the module. module_invoke($module, 'enable'); // Record the fact that it was enabled. $modules_enabled[] = $module; watchdog('system', '%module module enabled.', array('%module' => $module), WATCHDOG_INFO); } } // If any modules were newly installed, invoke hook_modules_installed(). if (!empty($modules_installed)) { module_invoke_all('modules_installed', $modules_installed); } // If any modules were newly enabled, invoke hook_modules_enabled(). if (!empty($modules_enabled)) { module_invoke_all('modules_enabled', $modules_enabled); } return TRUE; } /** * Disables a given set of modules. * * @param $module_list * An array of module names. * @param $disable_dependents * If TRUE, dependent modules will automatically be added and disabled in the * correct order. This incurs a significant performance cost, so use FALSE * if you know $module_list is already complete and in the correct order. */ function module_disable($module_list, $disable_dependents = TRUE) { if ($disable_dependents) { // Get all module data so we can find dependents and sort. $module_data = system_rebuild_module_data(); // Create an associative array with weights as values. $module_list = array_flip(array_values($module_list)); $profile = drupal_get_profile(); while (list($module) = each($module_list)) { if (!isset($module_data[$module]) || !$module_data[$module]->status) { // This module doesn't exist or is already disabled, skip it. unset($module_list[$module]); continue; } $module_list[$module] = $module_data[$module]->sort; // Add dependent modules to the list, with a placeholder weight. // The new modules will be processed as the while loop continues. foreach ($module_data[$module]->required_by as $dependent => $dependent_data) { if (!isset($module_list[$dependent]) && $dependent != $profile) { $module_list[$dependent] = 0; } } } // Sort the module list by pre-calculated weights. asort($module_list); $module_list = array_keys($module_list); } $invoke_modules = array(); foreach ($module_list as $module) { if (module_exists($module)) { // Check if node_access table needs rebuilding. if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) { node_access_needs_rebuild(TRUE); } module_load_install($module); module_invoke($module, 'disable'); db_update('system') ->fields(array('status' => 0)) ->condition('type', 'module') ->condition('name', $module) ->execute(); $invoke_modules[] = $module; watchdog('system', '%module module disabled.', array('%module' => $module), WATCHDOG_INFO); } } if (!empty($invoke_modules)) { // Refresh the module list to exclude the disabled modules. system_list_reset(); module_list(TRUE); module_implements('', FALSE, TRUE); entity_info_cache_clear(); // Invoke hook_modules_disabled before disabling modules, // so we can still call module hooks to get information. module_invoke_all('modules_disabled', $invoke_modules); // Update the registry to remove the newly-disabled module. registry_update(); _system_update_bootstrap_status(); // Update the theme registry to remove the newly-disabled module. drupal_theme_rebuild(); } // If there remains no more node_access module, rebuilding will be // straightforward, we can do it right now. if (node_access_needs_rebuild() && count(module_implements('node_grants')) == 0) { node_access_rebuild(); } } /** * @defgroup hooks Hooks * @{ * Allow modules to interact with the Drupal core. * * Drupal's module system is based on the concept of "hooks". A hook is a PHP * function that is named foo_bar(), where "foo" is the name of the module * (whose filename is thus foo.module) and "bar" is the name of the hook. Each * hook has a defined set of parameters and a specified result type. * * To extend Drupal, a module need simply implement a hook. When Drupal wishes * to allow intervention from modules, it determines which modules implement a * hook and calls that hook in all enabled modules that implement it. * * The available hooks to implement are explained here in the Hooks section of * the developer documentation. The string "hook" is used as a placeholder for * the module name in the hook definitions. For example, if the module file is * called example.module, then hook_help() as implemented by that module would * be defined as example_help(). * * The example functions included are not part of the Drupal core, they are * just models that you can modify. Only the hooks implemented within modules * are executed when running Drupal. * * @see themeable * @see callbacks */ /** * @defgroup callbacks Callbacks * @{ * Callback function signatures. * * Drupal's API sometimes uses callback functions to allow you to define how * some type of processing happens. A callback is a function with a defined * signature, which you define in a module. Then you pass the function name as * a parameter to a Drupal API function or return it as part of a hook * implementation return value, and your function is called at an appropriate * time. For instance, when setting up batch processing you might need to * provide a callback function for each processing step and/or a callback for * when processing is finished; you would do that by defining these functions * and passing their names into the batch setup function. * * Callback function signatures, like hook definitions, are described by * creating and documenting dummy functions in a *.api.php file; normally, the * dummy callback function's name should start with "callback_", and you should * document the parameters and return value and provide a sample function body. * Then your API documentation can refer to this callback function in its * documentation. A user of your API can usually name their callback function * anything they want, although a standard name would be to replace "callback_" * with the module name. * * @see hooks * @see themeable * * @} */ /** * Determines whether a module implements a hook. * * @param $module * The name of the module (without the .module extension). * @param $hook * The name of the hook (e.g. "help" or "menu"). * * @return * TRUE if the module is both installed and enabled, and the hook is * implemented in that module. */ function module_hook($module, $hook) { $function = $module . '_' . $hook; if (function_exists($function)) { return TRUE; } // If the hook implementation does not exist, check whether it may live in an // optional include file registered via hook_hook_info(). $hook_info = module_hook_info(); if (isset($hook_info[$hook]['group'])) { module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); if (function_exists($function)) { return TRUE; } } return FALSE; } /** * Determines which modules are implementing a hook. * * @param $hook * The name of the hook (e.g. "help" or "menu"). * @param $sort * By default, modules are ordered by weight and filename, settings this option * to TRUE, module list will be ordered by module name. * @param $reset * For internal use only: Whether to force the stored list of hook * implementations to be regenerated (such as after enabling a new module, * before processing hook_enable). * * @return * An array with the names of the modules which are implementing this hook. * * @see module_implements_write_cache() */ function module_implements($hook, $sort = FALSE, $reset = FALSE) { // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['implementations'] = &drupal_static(__FUNCTION__); } $implementations = &$drupal_static_fast['implementations']; // We maintain a persistent cache of hook implementations in addition to the // static cache to avoid looping through every module and every hook on each // request. Benchmarks show that the benefit of this caching outweighs the // additional database hit even when using the default database caching // backend and only a small number of modules are enabled. The cost of the // cache_get() is more or less constant and reduced further when non-database // caching backends are used, so there will be more significant gains when a // large number of modules are installed or hooks invoked, since this can // quickly lead to module_hook() being called several thousand times // per request. if ($reset) { $implementations = array(); cache_set('module_implements', array(), 'cache_bootstrap'); drupal_static_reset('module_hook_info'); drupal_static_reset('drupal_alter'); cache_clear_all('hook_info', 'cache_bootstrap'); return; } // Fetch implementations from cache. if (empty($implementations)) { $implementations = cache_get('module_implements', 'cache_bootstrap'); if ($implementations === FALSE) { $implementations = array(); } else { $implementations = $implementations->data; } } if (!isset($implementations[$hook])) { // The hook is not cached, so ensure that whether or not it has // implementations, that the cache is updated at the end of the request. $implementations['#write_cache'] = TRUE; $hook_info = module_hook_info(); $implementations[$hook] = array(); $list = module_list(FALSE, FALSE, $sort); foreach ($list as $module) { $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); // Since module_hook() may needlessly try to load the include file again, // function_exists() is used directly here. if (function_exists($module . '_' . $hook)) { $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; } } // Allow modules to change the weight of specific implementations but avoid // an infinite loop. if ($hook != 'module_implements_alter') { drupal_alter('module_implements', $implementations[$hook], $hook); } } else { foreach ($implementations[$hook] as $module => $group) { // If this hook implementation is stored in a lazy-loaded file, so include // that file first. if ($group) { module_load_include('inc', $module, "$module.$group"); } // It is possible that a module removed a hook implementation without the // implementations cache being rebuilt yet, so we check whether the // function exists on each request to avoid undefined function errors. // Since module_hook() may needlessly try to load the include file again, // function_exists() is used directly here. if (!function_exists($module . '_' . $hook)) { // Clear out the stale implementation from the cache and force a cache // refresh to forget about no longer existing hook implementations. unset($implementations[$hook][$module]); $implementations['#write_cache'] = TRUE; } } } return array_keys($implementations[$hook]); } /** * Retrieves a list of hooks that are declared through hook_hook_info(). * * @return * An associative array whose keys are hook names and whose values are an * associative array containing a group name. The structure of the array * is the same as the return value of hook_hook_info(). * * @see hook_hook_info() */ function module_hook_info() { // This function is indirectly invoked from bootstrap_invoke_all(), in which // case common.inc, subsystems, and modules are not loaded yet, so it does not // make sense to support hook groups resp. lazy-loaded include files prior to // full bootstrap. if (drupal_bootstrap(NULL, FALSE) != DRUPAL_BOOTSTRAP_FULL) { return array(); } $hook_info = &drupal_static(__FUNCTION__); if (!isset($hook_info)) { $hook_info = array(); $cache = cache_get('hook_info', 'cache_bootstrap'); if ($cache === FALSE) { // Rebuild the cache and save it. // We can't use module_invoke_all() here or it would cause an infinite // loop. foreach (module_list() as $module) { $function = $module . '_hook_info'; if (function_exists($function)) { $result = $function(); if (isset($result) && is_array($result)) { $hook_info = array_merge_recursive($hook_info, $result); } } } // We can't use drupal_alter() for the same reason as above. foreach (module_list() as $module) { $function = $module . '_hook_info_alter'; if (function_exists($function)) { $function($hook_info); } } cache_set('hook_info', $hook_info, 'cache_bootstrap'); } else { $hook_info = $cache->data; } } return $hook_info; } /** * Writes the hook implementation cache. * * @see module_implements() */ function module_implements_write_cache() { $implementations = &drupal_static('module_implements'); if (isset($implementations['#write_cache'])) { unset($implementations['#write_cache']); cache_set('module_implements', $implementations, 'cache_bootstrap'); } } /** * Invokes a hook in a particular module. * * All arguments are passed by value. Use drupal_alter() if you need to pass * arguments by reference. * * @param $module * The name of the module (without the .module extension). * @param $hook * The name of the hook to invoke. * @param ... * Arguments to pass to the hook implementation. * * @return * The return value of the hook implementation. * * @see drupal_alter() */ function module_invoke($module, $hook) { $args = func_get_args(); // Remove $module and $hook from the arguments. unset($args[0], $args[1]); if (module_hook($module, $hook)) { return call_user_func_array($module . '_' . $hook, $args); } } /** * Invokes a hook in all enabled modules that implement it. * * All arguments are passed by value. Use drupal_alter() if you need to pass * arguments by reference. * * @param $hook * The name of the hook to invoke. * @param ... * Arguments to pass to the hook. * * @return * An array of return values of the hook implementations. If modules return * arrays from their implementations, those are merged into one array. * * @see drupal_alter() */ function module_invoke_all($hook) { $args = func_get_args(); // Remove $hook from the arguments. unset($args[0]); $return = array(); foreach (module_implements($hook) as $module) { $function = $module . '_' . $hook; if (function_exists($function)) { $result = call_user_func_array($function, $args); if (isset($result) && is_array($result)) { $return = array_merge_recursive($return, $result); } elseif (isset($result)) { $return[] = $result; } } } return $return; } /** * @} End of "defgroup hooks". */ /** * Returns an array of modules required by core. */ function drupal_required_modules() { $files = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'modules', 'name', 0); $required = array(); // An installation profile is required and one must always be loaded. $required[] = drupal_get_profile(); foreach ($files as $name => $file) { $info = drupal_parse_info_file($file->uri); if (!empty($info) && !empty($info['required']) && $info['required']) { $required[] = $name; } } return $required; } /** * Passes alterable variables to specific hook_TYPE_alter() implementations. * * This dispatch function hands off the passed-in variables to type-specific * hook_TYPE_alter() implementations in modules. It ensures a consistent * interface for all altering operations. * * A maximum of 2 alterable arguments is supported (a third is supported for * legacy reasons, but should not be used in new code). In case more arguments * need to be passed and alterable, modules provide additional variables * assigned by reference in the last $context argument: * @code * $context = array( * 'alterable' => &$alterable, * 'unalterable' => $unalterable, * 'foo' => 'bar', * ); * drupal_alter('mymodule_data', $alterable1, $alterable2, $context); * @endcode * * Note that objects are always passed by reference in PHP5. If it is absolutely * required that no implementation alters a passed object in $context, then an * object needs to be cloned: * @code * $context = array( * 'unalterable_object' => clone $object, * ); * drupal_alter('mymodule_data', $data, $context); * @endcode * * @param $type * A string describing the type of the alterable $data. 'form', 'links', * 'node_content', and so on are several examples. Alternatively can be an * array, in which case hook_TYPE_alter() is invoked for each value in the * array, ordered first by module, and then for each module, in the order of * values in $type. For example, when Form API is using drupal_alter() to * execute both hook_form_alter() and hook_form_FORM_ID_alter() * implementations, it passes array('form', 'form_' . $form_id) for $type. * @param $data * The variable that will be passed to hook_TYPE_alter() implementations to be * altered. The type of this variable depends on the value of the $type * argument. For example, when altering a 'form', $data will be a structured * array. When altering a 'profile', $data will be an object. * @param $context1 * (optional) An additional variable that is passed by reference. * @param $context2 * (optional) An additional variable that is passed by reference. If more * context needs to be provided to implementations, then this should be an * associative array as described above. * @param $context3 * (optional) An additional variable that is passed by reference. This * parameter is deprecated and will not exist in Drupal 8; consequently, it * should not be used for new Drupal 7 code either. It is here only for * backwards compatibility with older code that passed additional arguments * to drupal_alter(). */ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL) { // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['functions'] = &drupal_static(__FUNCTION__); } $functions = &$drupal_static_fast['functions']; // Most of the time, $type is passed as a string, so for performance, // normalize it to that. When passed as an array, usually the first item in // the array is a generic type, and additional items in the array are more // specific variants of it, as in the case of array('form', 'form_FORM_ID'). if (is_array($type)) { $cid = implode(',', $type); $extra_types = $type; $type = array_shift($extra_types); // Allow if statements in this function to use the faster isset() rather // than !empty() both when $type is passed as a string, or as an array with // one item. if (empty($extra_types)) { unset($extra_types); } } else { $cid = $type; } // Some alter hooks are invoked many times per page request, so statically // cache the list of functions to call, and on subsequent calls, iterate // through them quickly. if (!isset($functions[$cid])) { $functions[$cid] = array(); $hook = $type . '_alter'; $modules = module_implements($hook); if (!isset($extra_types)) { // For the more common case of a single hook, we do not need to call // function_exists(), since module_implements() returns only modules with // implementations. foreach ($modules as $module) { $functions[$cid][] = $module . '_' . $hook; } } else { // For multiple hooks, we need $modules to contain every module that // implements at least one of them. $extra_modules = array(); foreach ($extra_types as $extra_type) { $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter')); } // If any modules implement one of the extra hooks that do not implement // the primary hook, we need to add them to the $modules array in their // appropriate order. module_implements() can only return ordered // implementations of a single hook. To get the ordered implementations // of multiple hooks, we mimic the module_implements() logic of first // ordering by module_list(), and then calling // drupal_alter('module_implements'). if (array_diff($extra_modules, $modules)) { // Merge the arrays and order by module_list(). $modules = array_intersect(module_list(), array_merge($modules, $extra_modules)); // Since module_implements() already took care of loading the necessary // include files, we can safely pass FALSE for the array values. $implementations = array_fill_keys($modules, FALSE); // Let modules adjust the order solely based on the primary hook. This // ensures the same module order regardless of whether this if block // runs. Calling drupal_alter() recursively in this way does not result // in an infinite loop, because this call is for a single $type, so we // won't end up in this code block again. drupal_alter('module_implements', $implementations, $hook); $modules = array_keys($implementations); } foreach ($modules as $module) { // Since $modules is a merged array, for any given module, we do not // know whether it has any particular implementation, so we need a // function_exists(). $function = $module . '_' . $hook; if (function_exists($function)) { $functions[$cid][] = $function; } foreach ($extra_types as $extra_type) { $function = $module . '_' . $extra_type . '_alter'; if (function_exists($function)) { $functions[$cid][] = $function; } } } } // Allow the theme to alter variables after the theme system has been // initialized. global $theme, $base_theme_info; if (isset($theme)) { $theme_keys = array(); foreach ($base_theme_info as $base) { $theme_keys[] = $base->name; } $theme_keys[] = $theme; foreach ($theme_keys as $theme_key) { $function = $theme_key . '_' . $hook; if (function_exists($function)) { $functions[$cid][] = $function; } if (isset($extra_types)) { foreach ($extra_types as $extra_type) { $function = $theme_key . '_' . $extra_type . '_alter'; if (function_exists($function)) { $functions[$cid][] = $function; } } } } } } foreach ($functions[$cid] as $function) { $function($data, $context1, $context2, $context3); } } drupal-7.26/includes/stream_wrappers.inc0000644001412200141220000005520512265562324017746 0ustar benderbenderuri = $uri; } /** * Base implementation of getUri(). */ function getUri() { return $this->uri; } /** * Returns the local writable target of the resource within the stream. * * This function should be used in place of calls to realpath() or similar * functions when attempting to determine the location of a file. While * functions like realpath() may return the location of a read-only file, this * method may return a URI or path suitable for writing that is completely * separate from the URI used for reading. * * @param $uri * Optional URI. * * @return * Returns a string representing a location suitable for writing of a file, * or FALSE if unable to write to the file such as with read-only streams. */ protected function getTarget($uri = NULL) { if (!isset($uri)) { $uri = $this->uri; } list($scheme, $target) = explode('://', $uri, 2); // Remove erroneous leading or trailing, forward-slashes and backslashes. return trim($target, '\/'); } /** * Base implementation of getMimeType(). */ static function getMimeType($uri, $mapping = NULL) { if (!isset($mapping)) { // The default file map, defined in file.mimetypes.inc is quite big. // We only load it when necessary. include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc'; $mapping = file_mimetype_mapping(); } $extension = ''; $file_parts = explode('.', drupal_basename($uri)); // Remove the first part: a full filename should not match an extension. array_shift($file_parts); // Iterate over the file parts, trying to find a match. // For my.awesome.image.jpeg, we try: // - jpeg // - image.jpeg, and // - awesome.image.jpeg while ($additional_part = array_pop($file_parts)) { $extension = strtolower($additional_part . ($extension ? '.' . $extension : '')); if (isset($mapping['extensions'][$extension])) { return $mapping['mimetypes'][$mapping['extensions'][$extension]]; } } return 'application/octet-stream'; } /** * Base implementation of chmod(). */ function chmod($mode) { $output = @chmod($this->getLocalPath(), $mode); // We are modifying the underlying file here, so we have to clear the stat // cache so that PHP understands that URI has changed too. clearstatcache(); return $output; } /** * Base implementation of realpath(). */ function realpath() { return $this->getLocalPath(); } /** * Returns the canonical absolute path of the URI, if possible. * * @param string $uri * (optional) The stream wrapper URI to be converted to a canonical * absolute path. This may point to a directory or another type of file. * * @return string|false * If $uri is not set, returns the canonical absolute path of the URI * previously set by the DrupalStreamWrapperInterface::setUri() function. * If $uri is set and valid for this class, returns its canonical absolute * path, as determined by the realpath() function. If $uri is set but not * valid, returns FALSE. */ protected function getLocalPath($uri = NULL) { if (!isset($uri)) { $uri = $this->uri; } $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri); $realpath = realpath($path); if (!$realpath) { // This file does not yet exist. $realpath = realpath(dirname($path)) . '/' . drupal_basename($path); } $directory = realpath($this->getDirectoryPath()); if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) { return FALSE; } return $realpath; } /** * Support for fopen(), file_get_contents(), file_put_contents() etc. * * @param $uri * A string containing the URI to the file to open. * @param $mode * The file mode ("r", "wb" etc.). * @param $options * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS. * @param $opened_path * A string containing the path actually opened. * * @return * Returns TRUE if file was opened successfully. * * @see http://php.net/manual/streamwrapper.stream-open.php */ public function stream_open($uri, $mode, $options, &$opened_path) { $this->uri = $uri; $path = $this->getLocalPath(); $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode); if ((bool) $this->handle && $options & STREAM_USE_PATH) { $opened_path = $path; } return (bool) $this->handle; } /** * Support for flock(). * * @param $operation * One of the following: * - LOCK_SH to acquire a shared lock (reader). * - LOCK_EX to acquire an exclusive lock (writer). * - LOCK_UN to release a lock (shared or exclusive). * - LOCK_NB if you don't want flock() to block while locking (not * supported on Windows). * * @return * Always returns TRUE at the present time. * * @see http://php.net/manual/streamwrapper.stream-lock.php */ public function stream_lock($operation) { if (in_array($operation, array(LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB))) { return flock($this->handle, $operation); } return TRUE; } /** * Support for fread(), file_get_contents() etc. * * @param $count * Maximum number of bytes to be read. * * @return * The string that was read, or FALSE in case of an error. * * @see http://php.net/manual/streamwrapper.stream-read.php */ public function stream_read($count) { return fread($this->handle, $count); } /** * Support for fwrite(), file_put_contents() etc. * * @param $data * The string to be written. * * @return * The number of bytes written (integer). * * @see http://php.net/manual/streamwrapper.stream-write.php */ public function stream_write($data) { return fwrite($this->handle, $data); } /** * Support for feof(). * * @return * TRUE if end-of-file has been reached. * * @see http://php.net/manual/streamwrapper.stream-eof.php */ public function stream_eof() { return feof($this->handle); } /** * Support for fseek(). * * @param $offset * The byte offset to got to. * @param $whence * SEEK_SET, SEEK_CUR, or SEEK_END. * * @return * TRUE on success. * * @see http://php.net/manual/streamwrapper.stream-seek.php */ public function stream_seek($offset, $whence) { // fseek returns 0 on success and -1 on a failure. // stream_seek 1 on success and 0 on a failure. return !fseek($this->handle, $offset, $whence); } /** * Support for fflush(). * * @return * TRUE if data was successfully stored (or there was no data to store). * * @see http://php.net/manual/streamwrapper.stream-flush.php */ public function stream_flush() { return fflush($this->handle); } /** * Support for ftell(). * * @return * The current offset in bytes from the beginning of file. * * @see http://php.net/manual/streamwrapper.stream-tell.php */ public function stream_tell() { return ftell($this->handle); } /** * Support for fstat(). * * @return * An array with file status, or FALSE in case of an error - see fstat() * for a description of this array. * * @see http://php.net/manual/streamwrapper.stream-stat.php */ public function stream_stat() { return fstat($this->handle); } /** * Support for fclose(). * * @return * TRUE if stream was successfully closed. * * @see http://php.net/manual/streamwrapper.stream-close.php */ public function stream_close() { return fclose($this->handle); } /** * Support for unlink(). * * @param $uri * A string containing the URI to the resource to delete. * * @return * TRUE if resource was successfully deleted. * * @see http://php.net/manual/streamwrapper.unlink.php */ public function unlink($uri) { $this->uri = $uri; return drupal_unlink($this->getLocalPath()); } /** * Support for rename(). * * @param $from_uri, * The URI to the file to rename. * @param $to_uri * The new URI for file. * * @return * TRUE if file was successfully renamed. * * @see http://php.net/manual/streamwrapper.rename.php */ public function rename($from_uri, $to_uri) { return rename($this->getLocalPath($from_uri), $this->getLocalPath($to_uri)); } /** * Gets the name of the directory from a given path. * * This method is usually accessed through drupal_dirname(), which wraps * around the PHP dirname() function because it does not support stream * wrappers. * * @param $uri * A URI or path. * * @return * A string containing the directory name. * * @see drupal_dirname() */ public function dirname($uri = NULL) { list($scheme, $target) = explode('://', $uri, 2); $target = $this->getTarget($uri); $dirname = dirname($target); if ($dirname == '.') { $dirname = ''; } return $scheme . '://' . $dirname; } /** * Support for mkdir(). * * @param $uri * A string containing the URI to the directory to create. * @param $mode * Permission flags - see mkdir(). * @param $options * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE. * * @return * TRUE if directory was successfully created. * * @see http://php.net/manual/streamwrapper.mkdir.php */ public function mkdir($uri, $mode, $options) { $this->uri = $uri; $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE); if ($recursive) { // $this->getLocalPath() fails if $uri has multiple levels of directories // that do not yet exist. $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri); } else { $localpath = $this->getLocalPath($uri); } if ($options & STREAM_REPORT_ERRORS) { return mkdir($localpath, $mode, $recursive); } else { return @mkdir($localpath, $mode, $recursive); } } /** * Support for rmdir(). * * @param $uri * A string containing the URI to the directory to delete. * @param $options * A bit mask of STREAM_REPORT_ERRORS. * * @return * TRUE if directory was successfully removed. * * @see http://php.net/manual/streamwrapper.rmdir.php */ public function rmdir($uri, $options) { $this->uri = $uri; if ($options & STREAM_REPORT_ERRORS) { return drupal_rmdir($this->getLocalPath()); } else { return @drupal_rmdir($this->getLocalPath()); } } /** * Support for stat(). * * @param $uri * A string containing the URI to get information about. * @param $flags * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET. * * @return * An array with file status, or FALSE in case of an error - see fstat() * for a description of this array. * * @see http://php.net/manual/streamwrapper.url-stat.php */ public function url_stat($uri, $flags) { $this->uri = $uri; $path = $this->getLocalPath(); // Suppress warnings if requested or if the file or directory does not // exist. This is consistent with PHP's plain filesystem stream wrapper. if ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) { return @stat($path); } else { return stat($path); } } /** * Support for opendir(). * * @param $uri * A string containing the URI to the directory to open. * @param $options * Unknown (parameter is not documented in PHP Manual). * * @return * TRUE on success. * * @see http://php.net/manual/streamwrapper.dir-opendir.php */ public function dir_opendir($uri, $options) { $this->uri = $uri; $this->handle = opendir($this->getLocalPath()); return (bool) $this->handle; } /** * Support for readdir(). * * @return * The next filename, or FALSE if there are no more files in the directory. * * @see http://php.net/manual/streamwrapper.dir-readdir.php */ public function dir_readdir() { return readdir($this->handle); } /** * Support for rewinddir(). * * @return * TRUE on success. * * @see http://php.net/manual/streamwrapper.dir-rewinddir.php */ public function dir_rewinddir() { rewinddir($this->handle); // We do not really have a way to signal a failure as rewinddir() does not // have a return value and there is no way to read a directory handler // without advancing to the next file. return TRUE; } /** * Support for closedir(). * * @return * TRUE on success. * * @see http://php.net/manual/streamwrapper.dir-closedir.php */ public function dir_closedir() { closedir($this->handle); // We do not really have a way to signal a failure as closedir() does not // have a return value. return TRUE; } } /** * Drupal public (public://) stream wrapper class. * * Provides support for storing publicly accessible files with the Drupal file * interface. */ class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper { /** * Implements abstract public function getDirectoryPath() */ public function getDirectoryPath() { return variable_get('file_public_path', conf_path() . '/files'); } /** * Overrides getExternalUrl(). * * Return the HTML URI of a public file. */ function getExternalUrl() { $path = str_replace('\\', '/', $this->getTarget()); return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path); } } /** * Drupal private (private://) stream wrapper class. * * Provides support for storing privately accessible files with the Drupal file * interface. */ class DrupalPrivateStreamWrapper extends DrupalLocalStreamWrapper { /** * Implements abstract public function getDirectoryPath() */ public function getDirectoryPath() { return variable_get('file_private_path', ''); } /** * Overrides getExternalUrl(). * * Return the HTML URI of a private file. */ function getExternalUrl() { $path = str_replace('\\', '/', $this->getTarget()); return url('system/files/' . $path, array('absolute' => TRUE)); } } /** * Drupal temporary (temporary://) stream wrapper class. * * Provides support for storing temporarily accessible files with the Drupal * file interface. * * Extends DrupalPublicStreamWrapper. */ class DrupalTemporaryStreamWrapper extends DrupalLocalStreamWrapper { /** * Implements abstract public function getDirectoryPath() */ public function getDirectoryPath() { return variable_get('file_temporary_path', file_directory_temp()); } /** * Overrides getExternalUrl(). */ public function getExternalUrl() { $path = str_replace('\\', '/', $this->getTarget()); return url('system/temporary/' . $path, array('absolute' => TRUE)); } } drupal-7.26/includes/theme.maintenance.inc0000644001412200141220000001563612265562324020117 0ustar benderbenderbase_theme)) { $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme]; $ancestor = $themes[$ancestor]->base_theme; } _drupal_theme_initialize($themes[$theme], array_reverse($base_theme), '_theme_load_offline_registry'); // These are usually added from system_init() -except maintenance.css. // When the database is inactive it's not called so we add it here. $path = drupal_get_path('module', 'system'); drupal_add_css($path . '/system.base.css'); drupal_add_css($path . '/system.admin.css'); drupal_add_css($path . '/system.menus.css'); drupal_add_css($path . '/system.messages.css'); drupal_add_css($path . '/system.theme.css'); drupal_add_css($path . '/system.maintenance.css'); } /** * Builds the registry when the site needs to bypass any database calls. */ function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) { return _theme_build_registry($theme, $base_theme, $theme_engine); } /** * Returns HTML for a list of maintenance tasks to perform. * * @param $variables * An associative array containing: * - items: An associative array of maintenance tasks. * - active: The key for the currently active maintenance task. * * @ingroup themeable */ function theme_task_list($variables) { $items = $variables['items']; $active = $variables['active']; $done = isset($items[$active]) || $active == NULL; $output = '

      Installation tasks

      '; $output .= '
        '; foreach ($items as $k => $item) { if ($active == $k) { $class = 'active'; $status = '(' . t('active') . ')'; $done = FALSE; } else { $class = $done ? 'done' : ''; $status = $done ? '(' . t('done') . ')' : ''; } $output .= ''; $output .= $item; $output .= ($status ? '' . $status . '' : ''); $output .= ''; } $output .= '
      '; return $output; } /** * Returns HTML for the installation page. * * Note: this function is not themeable. * * @param $variables * An associative array containing: * - content: The page content to show. */ function theme_install_page($variables) { drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); return theme('maintenance_page', $variables); } /** * Returns HTML for the update page. * * Note: this function is not themeable. * * @param $variables * An associative array containing: * - content: The page content to show. * - show_messages: Whether to output status and error messages. * FALSE can be useful to postpone the messages to a subsequent page. */ function theme_update_page($variables) { drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); return theme('maintenance_page', $variables); } /** * Returns HTML for a results report of an operation run by authorize.php. * * @param $variables * An associative array containing: * - messages: An array of result messages. * * @ingroup themeable */ function theme_authorize_report($variables) { $messages = $variables['messages']; $output = ''; if (!empty($messages)) { $output .= '
      '; foreach ($messages as $heading => $logs) { $items = array(); foreach ($logs as $number => $log_message) { if ($number === '#abort') { continue; } $items[] = theme('authorize_message', array('message' => $log_message['message'], 'success' => $log_message['success'])); } $output .= theme('item_list', array('items' => $items, 'title' => $heading)); } $output .= '
      '; } return $output; } /** * Returns HTML for a single log message from the authorize.php batch operation. * * @param $variables * An associative array containing: * - message: The log message. * - success: A boolean indicating failure or success. * * @ingroup themeable */ function theme_authorize_message($variables) { $message = $variables['message']; $success = $variables['success']; if ($success) { $item = array('data' => $message, 'class' => array('success')); } else { $item = array('data' => '' . $message . '', 'class' => array('failure')); } return $item; } drupal-7.26/includes/file.mimetypes.inc0000644001412200141220000005641312265562324017464 0ustar benderbender array( 0 => 'application/andrew-inset', 1 => 'application/atom', 2 => 'application/atomcat+xml', 3 => 'application/atomserv+xml', 4 => 'application/cap', 5 => 'application/cu-seeme', 6 => 'application/dsptype', 7 => 'application/hta', 8 => 'application/java-archive', 9 => 'application/java-serialized-object', 10 => 'application/java-vm', 11 => 'application/mac-binhex40', 12 => 'application/mathematica', 13 => 'application/msaccess', 14 => 'application/msword', 15 => 'application/octet-stream', 16 => 'application/oda', 17 => 'application/ogg', 18 => 'application/pdf', 19 => 'application/pgp-keys', 20 => 'application/pgp-signature', 21 => 'application/pics-rules', 22 => 'application/postscript', 23 => 'application/rar', 24 => 'application/rdf+xml', 25 => 'application/rss+xml', 26 => 'application/rtf', 27 => 'application/smil', 28 => 'application/vnd.cinderella', 29 => 'application/vnd.google-earth.kml+xml', 30 => 'application/vnd.google-earth.kmz', 31 => 'application/vnd.mozilla.xul+xml', 32 => 'application/vnd.ms-excel', 33 => 'application/vnd.ms-excel.addin.macroEnabled.12', 34 => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 35 => 'application/vnd.ms-excel.sheet.macroEnabled.12', 36 => 'application/vnd.ms-excel.template.macroEnabled.12', 37 => 'application/vnd.ms-pki.seccat', 38 => 'application/vnd.ms-pki.stl', 39 => 'application/vnd.ms-powerpoint', 40 => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', 41 => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 42 => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 43 => 'application/vnd.ms-powerpoint.template.macroEnabled.12', 44 => 'application/vnd.ms-word.document.macroEnabled.12', 45 => 'application/vnd.ms-word.template.macroEnabled.12', 46 => 'application/vnd.ms-xpsdocument', 47 => 'application/vnd.oasis.opendocument.chart', 48 => 'application/vnd.oasis.opendocument.database', 49 => 'application/vnd.oasis.opendocument.formula', 50 => 'application/vnd.oasis.opendocument.graphics', 51 => 'application/vnd.oasis.opendocument.graphics-template', 52 => 'application/vnd.oasis.opendocument.image', 53 => 'application/vnd.oasis.opendocument.presentation', 54 => 'application/vnd.oasis.opendocument.presentation-template', 55 => 'application/vnd.oasis.opendocument.spreadsheet', 56 => 'application/vnd.oasis.opendocument.spreadsheet-template', 57 => 'application/vnd.oasis.opendocument.text', 58 => 'application/vnd.oasis.opendocument.text-master', 59 => 'application/vnd.oasis.opendocument.text-template', 60 => 'application/vnd.oasis.opendocument.text-web', 61 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 62 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 63 => 'application/vnd.openxmlformats-officedocument.presentationml.template', 64 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 66 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 67 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 68 => 'application/vnd.rim.cod', 69 => 'application/vnd.smaf', 70 => 'application/vnd.stardivision.calc', 71 => 'application/vnd.stardivision.chart', 72 => 'application/vnd.stardivision.draw', 73 => 'application/vnd.stardivision.impress', 74 => 'application/vnd.stardivision.math', 75 => 'application/vnd.stardivision.writer', 76 => 'application/vnd.stardivision.writer-global', 77 => 'application/vnd.sun.xml.calc', 78 => 'application/vnd.sun.xml.calc.template', 79 => 'application/vnd.sun.xml.draw', 80 => 'application/vnd.sun.xml.draw.template', 81 => 'application/vnd.sun.xml.impress', 82 => 'application/vnd.sun.xml.impress.template', 83 => 'application/vnd.sun.xml.math', 84 => 'application/vnd.sun.xml.writer', 85 => 'application/vnd.sun.xml.writer.global', 86 => 'application/vnd.sun.xml.writer.template', 87 => 'application/vnd.symbian.install', 88 => 'application/vnd.visio', 89 => 'application/vnd.wap.wbxml', 90 => 'application/vnd.wap.wmlc', 91 => 'application/vnd.wap.wmlscriptc', 92 => 'application/wordperfect', 93 => 'application/wordperfect5.1', 94 => 'application/x-123', 95 => 'application/x-7z-compressed', 96 => 'application/x-abiword', 97 => 'application/x-apple-diskimage', 98 => 'application/x-bcpio', 99 => 'application/x-bittorrent', 100 => 'application/x-cab', 101 => 'application/x-cbr', 102 => 'application/x-cbz', 103 => 'application/x-cdf', 104 => 'application/x-cdlink', 105 => 'application/x-chess-pgn', 106 => 'application/x-cpio', 107 => 'application/x-debian-package', 108 => 'application/x-director', 109 => 'application/x-dms', 110 => 'application/x-doom', 111 => 'application/x-dvi', 112 => 'application/x-flac', 113 => 'application/x-font', 114 => 'application/x-freemind', 115 => 'application/x-futuresplash', 116 => 'application/x-gnumeric', 117 => 'application/x-go-sgf', 118 => 'application/x-graphing-calculator', 119 => 'application/x-gtar', 120 => 'application/x-hdf', 121 => 'application/x-httpd-eruby', 122 => 'application/x-httpd-php', 123 => 'application/x-httpd-php-source', 124 => 'application/x-httpd-php3', 125 => 'application/x-httpd-php3-preprocessed', 126 => 'application/x-httpd-php4', 127 => 'application/x-ica', 128 => 'application/x-internet-signup', 129 => 'application/x-iphone', 130 => 'application/x-iso9660-image', 131 => 'application/x-java-jnlp-file', 132 => 'application/x-javascript', 133 => 'application/x-jmol', 134 => 'application/x-kchart', 135 => 'application/x-killustrator', 136 => 'application/x-koan', 137 => 'application/x-kpresenter', 138 => 'application/x-kspread', 139 => 'application/x-kword', 140 => 'application/x-latex', 141 => 'application/x-lha', 142 => 'application/x-lyx', 143 => 'application/x-lzh', 144 => 'application/x-lzx', 145 => 'application/x-maker', 146 => 'application/x-mif', 147 => 'application/x-ms-wmd', 148 => 'application/x-ms-wmz', 149 => 'application/x-msdos-program', 150 => 'application/x-msi', 151 => 'application/x-netcdf', 152 => 'application/x-ns-proxy-autoconfig', 153 => 'application/x-nwc', 154 => 'application/x-object', 155 => 'application/x-oz-application', 156 => 'application/x-pkcs7-certreqresp', 157 => 'application/x-pkcs7-crl', 158 => 'application/x-python-code', 159 => 'application/x-quicktimeplayer', 160 => 'application/x-redhat-package-manager', 161 => 'application/x-shar', 162 => 'application/x-shockwave-flash', 163 => 'application/x-stuffit', 164 => 'application/x-sv4cpio', 165 => 'application/x-sv4crc', 166 => 'application/x-tar', 167 => 'application/x-tcl', 168 => 'application/x-tex-gf', 169 => 'application/x-tex-pk', 170 => 'application/x-texinfo', 171 => 'application/x-trash', 172 => 'application/x-troff', 173 => 'application/x-troff-man', 174 => 'application/x-troff-me', 175 => 'application/x-troff-ms', 176 => 'application/x-ustar', 177 => 'application/x-wais-source', 178 => 'application/x-wingz', 179 => 'application/x-x509-ca-cert', 180 => 'application/x-xcf', 181 => 'application/x-xfig', 182 => 'application/x-xpinstall', 183 => 'application/xhtml+xml', 184 => 'application/xml', 185 => 'application/zip', 186 => 'audio/basic', 187 => 'audio/midi', 346 => 'audio/mp4', 188 => 'audio/mpeg', 189 => 'audio/ogg', 190 => 'audio/prs.sid', 191 => 'audio/x-aiff', 192 => 'audio/x-gsm', 193 => 'audio/x-mpegurl', 194 => 'audio/x-ms-wax', 195 => 'audio/x-ms-wma', 196 => 'audio/x-pn-realaudio', 197 => 'audio/x-realaudio', 198 => 'audio/x-scpls', 199 => 'audio/x-sd2', 200 => 'audio/x-wav', 201 => 'chemical/x-alchemy', 202 => 'chemical/x-cache', 203 => 'chemical/x-cache-csf', 204 => 'chemical/x-cactvs-binary', 205 => 'chemical/x-cdx', 206 => 'chemical/x-cerius', 207 => 'chemical/x-chem3d', 208 => 'chemical/x-chemdraw', 209 => 'chemical/x-cif', 210 => 'chemical/x-cmdf', 211 => 'chemical/x-cml', 212 => 'chemical/x-compass', 213 => 'chemical/x-crossfire', 214 => 'chemical/x-csml', 215 => 'chemical/x-ctx', 216 => 'chemical/x-cxf', 217 => 'chemical/x-embl-dl-nucleotide', 218 => 'chemical/x-galactic-spc', 219 => 'chemical/x-gamess-input', 220 => 'chemical/x-gaussian-checkpoint', 221 => 'chemical/x-gaussian-cube', 222 => 'chemical/x-gaussian-input', 223 => 'chemical/x-gaussian-log', 224 => 'chemical/x-gcg8-sequence', 225 => 'chemical/x-genbank', 226 => 'chemical/x-hin', 227 => 'chemical/x-isostar', 228 => 'chemical/x-jcamp-dx', 229 => 'chemical/x-kinemage', 230 => 'chemical/x-macmolecule', 231 => 'chemical/x-macromodel-input', 232 => 'chemical/x-mdl-molfile', 233 => 'chemical/x-mdl-rdfile', 234 => 'chemical/x-mdl-rxnfile', 235 => 'chemical/x-mdl-sdfile', 236 => 'chemical/x-mdl-tgf', 237 => 'chemical/x-mmcif', 238 => 'chemical/x-mol2', 239 => 'chemical/x-molconn-Z', 240 => 'chemical/x-mopac-graph', 241 => 'chemical/x-mopac-input', 242 => 'chemical/x-mopac-out', 243 => 'chemical/x-mopac-vib', 244 => 'chemical/x-ncbi-asn1-ascii', 245 => 'chemical/x-ncbi-asn1-binary', 246 => 'chemical/x-ncbi-asn1-spec', 247 => 'chemical/x-pdb', 248 => 'chemical/x-rosdal', 249 => 'chemical/x-swissprot', 250 => 'chemical/x-vamas-iso14976', 251 => 'chemical/x-vmd', 252 => 'chemical/x-xtel', 253 => 'chemical/x-xyz', 254 => 'image/gif', 255 => 'image/ief', 256 => 'image/jpeg', 257 => 'image/pcx', 258 => 'image/png', 259 => 'image/svg+xml', 260 => 'image/tiff', 261 => 'image/vnd.djvu', 262 => 'image/vnd.microsoft.icon', 263 => 'image/vnd.wap.wbmp', 264 => 'image/x-cmu-raster', 265 => 'image/x-coreldraw', 266 => 'image/x-coreldrawpattern', 267 => 'image/x-coreldrawtemplate', 268 => 'image/x-corelphotopaint', 269 => 'image/x-jg', 270 => 'image/x-jng', 271 => 'image/x-ms-bmp', 272 => 'image/x-photoshop', 273 => 'image/x-portable-anymap', 274 => 'image/x-portable-bitmap', 275 => 'image/x-portable-graymap', 276 => 'image/x-portable-pixmap', 277 => 'image/x-rgb', 278 => 'image/x-xbitmap', 279 => 'image/x-xpixmap', 280 => 'image/x-xwindowdump', 281 => 'message/rfc822', 282 => 'model/iges', 283 => 'model/mesh', 284 => 'model/vrml', 285 => 'text/calendar', 286 => 'text/css', 287 => 'text/csv', 288 => 'text/h323', 289 => 'text/html', 290 => 'text/iuls', 291 => 'text/mathml', 292 => 'text/plain', 293 => 'text/richtext', 294 => 'text/scriptlet', 295 => 'text/tab-separated-values', 296 => 'text/texmacs', 297 => 'text/vnd.sun.j2me.app-descriptor', 298 => 'text/vnd.wap.wml', 299 => 'text/vnd.wap.wmlscript', 300 => 'text/x-bibtex', 301 => 'text/x-boo', 302 => 'text/x-c++hdr', 303 => 'text/x-c++src', 304 => 'text/x-chdr', 305 => 'text/x-component', 306 => 'text/x-csh', 307 => 'text/x-csrc', 308 => 'text/x-diff', 309 => 'text/x-dsrc', 310 => 'text/x-haskell', 311 => 'text/x-java', 312 => 'text/x-literate-haskell', 313 => 'text/x-moc', 314 => 'text/x-pascal', 315 => 'text/x-pcs-gcd', 316 => 'text/x-perl', 317 => 'text/x-python', 318 => 'text/x-setext', 319 => 'text/x-sh', 320 => 'text/x-tcl', 321 => 'text/x-tex', 322 => 'text/x-vcalendar', 323 => 'text/x-vcard', 324 => 'video/3gpp', 325 => 'video/dl', 326 => 'video/dv', 327 => 'video/fli', 328 => 'video/gl', 329 => 'video/mp4', 330 => 'video/mpeg', 331 => 'video/ogg', 332 => 'video/quicktime', 333 => 'video/vnd.mpegurl', 347 => 'video/x-flv', 334 => 'video/x-la-asf', 348 => 'video/x-m4v', 335 => 'video/x-mng', 336 => 'video/x-ms-asf', 337 => 'video/x-ms-wm', 338 => 'video/x-ms-wmv', 339 => 'video/x-ms-wmx', 340 => 'video/x-ms-wvx', 341 => 'video/x-msvideo', 342 => 'video/x-sgi-movie', 343 => 'x-conference/x-cooltalk', 344 => 'x-epoc/x-sisx-app', 345 => 'x-world/x-vrml', ), // Extensions added to this list MUST be lower-case. 'extensions' => array( 'ez' => 0, 'atom' => 1, 'atomcat' => 2, 'atomsrv' => 3, 'cap' => 4, 'pcap' => 4, 'cu' => 5, 'tsp' => 6, 'hta' => 7, 'jar' => 8, 'ser' => 9, 'class' => 10, 'hqx' => 11, 'nb' => 12, 'mdb' => 13, 'dot' => 14, 'doc' => 14, 'bin' => 15, 'oda' => 16, 'ogx' => 17, 'pdf' => 18, 'key' => 19, 'pgp' => 20, 'prf' => 21, 'eps' => 22, 'ai' => 22, 'ps' => 22, 'rar' => 23, 'rdf' => 24, 'rss' => 25, 'rtf' => 26, 'smi' => 27, 'smil' => 27, 'cdy' => 28, 'kml' => 29, 'kmz' => 30, 'xul' => 31, 'xlb' => 32, 'xlt' => 32, 'xls' => 32, 'xlam' => 33, 'xlsb' => 34, 'xlsm' => 35, 'xltm' => 36, 'cat' => 37, 'stl' => 38, 'pps' => 39, 'ppt' => 39, 'ppam' => 40, 'pptm' => 41, 'ppsm' => 42, 'potm' => 43, 'docm' => 44, 'dotm' => 45, 'xps' => 46, 'odc' => 47, 'odb' => 48, 'odf' => 49, 'odg' => 50, 'otg' => 51, 'odi' => 52, 'odp' => 53, 'otp' => 54, 'ods' => 55, 'ots' => 56, 'odt' => 57, 'odm' => 58, 'ott' => 59, 'oth' => 60, 'pptx' => 61, 'ppsx' => 62, 'potx' => 63, 'xlsx' => 64, 'xltx' => 65, 'docx' => 66, 'dotx' => 67, 'cod' => 68, 'mmf' => 69, 'sdc' => 70, 'sds' => 71, 'sda' => 72, 'sdd' => 73, 'sdw' => 75, 'sgl' => 76, 'sxc' => 77, 'stc' => 78, 'sxd' => 79, 'std' => 80, 'sxi' => 81, 'sti' => 82, 'sxm' => 83, 'sxw' => 84, 'sxg' => 85, 'stw' => 86, 'sis' => 87, 'vsd' => 88, 'wbxml' => 89, 'wmlc' => 90, 'wmlsc' => 91, 'wpd' => 92, 'wp5' => 93, 'wk' => 94, '7z' => 95, 'abw' => 96, 'dmg' => 97, 'bcpio' => 98, 'torrent' => 99, 'cab' => 100, 'cbr' => 101, 'cbz' => 102, 'cdf' => 103, 'vcd' => 104, 'pgn' => 105, 'cpio' => 106, 'udeb' => 107, 'deb' => 107, 'dir' => 108, 'dxr' => 108, 'dcr' => 108, 'dms' => 109, 'wad' => 110, 'dvi' => 111, 'flac' => 112, 'pfa' => 113, 'pfb' => 113, 'pcf' => 113, 'gsf' => 113, 'pcf.z' => 113, 'mm' => 114, 'spl' => 115, 'gnumeric' => 116, 'sgf' => 117, 'gcf' => 118, 'taz' => 119, 'gtar' => 119, 'tgz' => 119, 'hdf' => 120, 'rhtml' => 121, 'phtml' => 122, 'pht' => 122, 'php' => 122, 'phps' => 123, 'php3' => 124, 'php3p' => 125, 'php4' => 126, 'ica' => 127, 'ins' => 128, 'isp' => 128, 'iii' => 129, 'iso' => 130, 'jnlp' => 131, 'js' => 132, 'jmz' => 133, 'chrt' => 134, 'kil' => 135, 'skp' => 136, 'skd' => 136, 'skm' => 136, 'skt' => 136, 'kpr' => 137, 'kpt' => 137, 'ksp' => 138, 'kwd' => 139, 'kwt' => 139, 'latex' => 140, 'lha' => 141, 'lyx' => 142, 'lzh' => 143, 'lzx' => 144, 'maker' => 145, 'frm' => 145, 'frame' => 145, 'fm' => 145, 'book' => 145, 'fb' => 145, 'fbdoc' => 145, 'mif' => 146, 'wmd' => 147, 'wmz' => 148, 'dll' => 149, 'bat' => 149, 'exe' => 149, 'com' => 149, 'msi' => 150, 'nc' => 151, 'pac' => 152, 'nwc' => 153, 'o' => 154, 'oza' => 155, 'p7r' => 156, 'crl' => 157, 'pyo' => 158, 'pyc' => 158, 'qtl' => 159, 'rpm' => 160, 'shar' => 161, 'swf' => 162, 'swfl' => 162, 'sitx' => 163, 'sit' => 163, 'sv4cpio' => 164, 'sv4crc' => 165, 'tar' => 166, 'gf' => 168, 'pk' => 169, 'texi' => 170, 'texinfo' => 170, 'sik' => 171, '~' => 171, 'bak' => 171, '%' => 171, 'old' => 171, 't' => 172, 'roff' => 172, 'tr' => 172, 'man' => 173, 'me' => 174, 'ms' => 175, 'ustar' => 176, 'src' => 177, 'wz' => 178, 'crt' => 179, 'xcf' => 180, 'fig' => 181, 'xpi' => 182, 'xht' => 183, 'xhtml' => 183, 'xml' => 184, 'xsl' => 184, 'zip' => 185, 'au' => 186, 'snd' => 186, 'mid' => 187, 'midi' => 187, 'kar' => 187, 'mpega' => 188, 'mpga' => 188, 'm4a' => 188, 'mp3' => 188, 'mp2' => 188, 'ogg' => 189, 'oga' => 189, 'spx' => 189, 'sid' => 190, 'aif' => 191, 'aiff' => 191, 'aifc' => 191, 'gsm' => 192, 'm3u' => 193, 'wax' => 194, 'wma' => 195, 'rm' => 196, 'ram' => 196, 'ra' => 197, 'pls' => 198, 'sd2' => 199, 'wav' => 200, 'alc' => 201, 'cac' => 202, 'cache' => 202, 'csf' => 203, 'cascii' => 204, 'cbin' => 204, 'ctab' => 204, 'cdx' => 205, 'cer' => 206, 'c3d' => 207, 'chm' => 208, 'cif' => 209, 'cmdf' => 210, 'cml' => 211, 'cpa' => 212, 'bsd' => 213, 'csml' => 214, 'csm' => 214, 'ctx' => 215, 'cxf' => 216, 'cef' => 216, 'emb' => 217, 'embl' => 217, 'spc' => 218, 'gam' => 219, 'inp' => 219, 'gamin' => 219, 'fchk' => 220, 'fch' => 220, 'cub' => 221, 'gau' => 222, 'gjf' => 222, 'gjc' => 222, 'gal' => 223, 'gcg' => 224, 'gen' => 225, 'hin' => 226, 'istr' => 227, 'ist' => 227, 'dx' => 228, 'jdx' => 228, 'kin' => 229, 'mcm' => 230, 'mmd' => 231, 'mmod' => 231, 'mol' => 232, 'rd' => 233, 'rxn' => 234, 'sdf' => 235, 'sd' => 235, 'tgf' => 236, 'mcif' => 237, 'mol2' => 238, 'b' => 239, 'gpt' => 240, 'mopcrt' => 241, 'zmt' => 241, 'mpc' => 241, 'dat' => 241, 'mop' => 241, 'moo' => 242, 'mvb' => 243, 'prt' => 244, 'aso' => 245, 'val' => 245, 'asn' => 246, 'ent' => 247, 'pdb' => 247, 'ros' => 248, 'sw' => 249, 'vms' => 250, 'vmd' => 251, 'xtel' => 252, 'xyz' => 253, 'gif' => 254, 'ief' => 255, 'jpeg' => 256, 'jpe' => 256, 'jpg' => 256, 'pcx' => 257, 'png' => 258, 'svgz' => 259, 'svg' => 259, 'tif' => 260, 'tiff' => 260, 'djvu' => 261, 'djv' => 261, 'ico' => 262, 'wbmp' => 263, 'ras' => 264, 'cdr' => 265, 'pat' => 266, 'cdt' => 267, 'cpt' => 268, 'art' => 269, 'jng' => 270, 'bmp' => 271, 'psd' => 272, 'pnm' => 273, 'pbm' => 274, 'pgm' => 275, 'ppm' => 276, 'rgb' => 277, 'xbm' => 278, 'xpm' => 279, 'xwd' => 280, 'eml' => 281, 'igs' => 282, 'iges' => 282, 'silo' => 283, 'msh' => 283, 'mesh' => 283, 'icz' => 285, 'ics' => 285, 'css' => 286, 'csv' => 287, '323' => 288, 'html' => 289, 'htm' => 289, 'shtml' => 289, 'uls' => 290, 'mml' => 291, 'txt' => 292, 'pot' => 292, 'text' => 292, 'asc' => 292, 'rtx' => 293, 'wsc' => 294, 'sct' => 294, 'tsv' => 295, 'ts' => 296, 'tm' => 296, 'jad' => 297, 'wml' => 298, 'wmls' => 299, 'bib' => 300, 'boo' => 301, 'hpp' => 302, 'hh' => 302, 'h++' => 302, 'hxx' => 302, 'cxx' => 303, 'cc' => 303, 'cpp' => 303, 'c++' => 303, 'h' => 304, 'htc' => 305, 'csh' => 306, 'c' => 307, 'patch' => 308, 'diff' => 308, 'd' => 309, 'hs' => 310, 'java' => 311, 'lhs' => 312, 'moc' => 313, 'pas' => 314, 'p' => 314, 'gcd' => 315, 'pm' => 316, 'pl' => 316, 'py' => 317, 'etx' => 318, 'sh' => 319, 'tk' => 320, 'tcl' => 320, 'cls' => 321, 'ltx' => 321, 'sty' => 321, 'tex' => 321, 'vcs' => 322, 'vcf' => 323, '3gp' => 324, 'dl' => 325, 'dif' => 326, 'dv' => 326, 'fli' => 327, 'gl' => 328, 'mp4' => 329, 'f4v' => 329, 'f4p' => 329, 'mpe' => 330, 'mpeg' => 330, 'mpg' => 330, 'ogv' => 331, 'qt' => 332, 'mov' => 332, 'mxu' => 333, 'lsf' => 334, 'lsx' => 334, 'mng' => 335, 'asx' => 336, 'asf' => 336, 'wm' => 337, 'wmv' => 338, 'wmx' => 339, 'wvx' => 340, 'avi' => 341, 'movie' => 342, 'ice' => 343, 'sisx' => 344, 'wrl' => 345, 'vrm' => 345, 'vrml' => 345, 'f4a' => 346, 'f4b' => 346, 'flv' => 347, 'm4v' => 348, ), ); } drupal-7.26/includes/unicode.entities.inc0000644001412200141220000001255712265562324020004 0ustar benderbender 'Á', 'á' => 'á', 'Â' => 'Â', 'â' => 'â', '´' => '´', 'Æ' => 'Æ', 'æ' => 'æ', 'À' => 'À', 'à' => 'à', 'ℵ' => 'ℵ', 'Α' => 'Α', 'α' => 'α', '&' => '&', '∧' => '∧', '∠' => '∠', 'Å' => 'Å', 'å' => 'å', '≈' => '≈', 'Ã' => 'Ã', 'ã' => 'ã', 'Ä' => 'Ä', 'ä' => 'ä', '„' => '„', 'Β' => 'Β', 'β' => 'β', '¦' => '¦', '•' => '•', '∩' => '∩', 'Ç' => 'Ç', 'ç' => 'ç', '¸' => '¸', '¢' => '¢', 'Χ' => 'Χ', 'χ' => 'χ', 'ˆ' => 'ˆ', '♣' => '♣', '≅' => '≅', '©' => '©', '↵' => '↵', '∪' => '∪', '¤' => '¤', '†' => '†', '‡' => '‡', '↓' => '↓', '⇓' => '⇓', '°' => '°', 'Δ' => 'Δ', 'δ' => 'δ', '♦' => '♦', '÷' => '÷', 'É' => 'É', 'é' => 'é', 'Ê' => 'Ê', 'ê' => 'ê', 'È' => 'È', 'è' => 'è', '∅' => '∅', ' ' => ' ', ' ' => ' ', 'Ε' => 'Ε', 'ε' => 'ε', '≡' => '≡', 'Η' => 'Η', 'η' => 'η', 'Ð' => 'Ð', 'ð' => 'ð', 'Ë' => 'Ë', 'ë' => 'ë', '€' => '€', '∃' => '∃', 'ƒ' => 'ƒ', '∀' => '∀', '½' => '½', '¼' => '¼', '¾' => '¾', '⁄' => '⁄', 'Γ' => 'Γ', 'γ' => 'γ', '≥' => '≥', '↔' => '↔', '⇔' => '⇔', '♥' => '♥', '…' => '…', 'Í' => 'Í', 'í' => 'í', 'Î' => 'Î', 'î' => 'î', '¡' => '¡', 'Ì' => 'Ì', 'ì' => 'ì', 'ℑ' => 'ℑ', '∞' => '∞', '∫' => '∫', 'Ι' => 'Ι', 'ι' => 'ι', '¿' => '¿', '∈' => '∈', 'Ï' => 'Ï', 'ï' => 'ï', 'Κ' => 'Κ', 'κ' => 'κ', 'Λ' => 'Λ', 'λ' => 'λ', '⟨' => '〈', '«' => '«', '←' => '←', '⇐' => '⇐', '⌈' => '⌈', '“' => '“', '≤' => '≤', '⌊' => '⌊', '∗' => '∗', '◊' => '◊', '‎' => '‎', '‹' => '‹', '‘' => '‘', '¯' => '¯', '—' => '—', 'µ' => 'µ', '·' => '·', '−' => '−', 'Μ' => 'Μ', 'μ' => 'μ', '∇' => '∇', ' ' => ' ', '–' => '–', '≠' => '≠', '∋' => '∋', '¬' => '¬', '∉' => '∉', '⊄' => '⊄', 'Ñ' => 'Ñ', 'ñ' => 'ñ', 'Ν' => 'Ν', 'ν' => 'ν', 'Ó' => 'Ó', 'ó' => 'ó', 'Ô' => 'Ô', 'ô' => 'ô', 'Œ' => 'Œ', 'œ' => 'œ', 'Ò' => 'Ò', 'ò' => 'ò', '‾' => '‾', 'Ω' => 'Ω', 'ω' => 'ω', 'Ο' => 'Ο', 'ο' => 'ο', '⊕' => '⊕', '∨' => '∨', 'ª' => 'ª', 'º' => 'º', 'Ø' => 'Ø', 'ø' => 'ø', 'Õ' => 'Õ', 'õ' => 'õ', '⊗' => '⊗', 'Ö' => 'Ö', 'ö' => 'ö', '¶' => '¶', '∂' => '∂', '‰' => '‰', '⊥' => '⊥', 'Φ' => 'Φ', 'φ' => 'φ', 'Π' => 'Π', 'π' => 'π', 'ϖ' => 'ϖ', '±' => '±', '£' => '£', '′' => '′', '″' => '″', '∏' => '∏', '∝' => '∝', 'Ψ' => 'Ψ', 'ψ' => 'ψ', '√' => '√', '⟩' => '〉', '»' => '»', '→' => '→', '⇒' => '⇒', '⌉' => '⌉', '”' => '”', 'ℜ' => 'ℜ', '®' => '®', '⌋' => '⌋', 'Ρ' => 'Ρ', 'ρ' => 'ρ', '‏' => '‏', '›' => '›', '’' => '’', '‚' => '‚', 'Š' => 'Š', 'š' => 'š', '⋅' => '⋅', '§' => '§', '­' => '­', 'Σ' => 'Σ', 'σ' => 'σ', 'ς' => 'ς', '∼' => '∼', '♠' => '♠', '⊂' => '⊂', '⊆' => '⊆', '∑' => '∑', '¹' => '¹', '²' => '²', '³' => '³', '⊃' => '⊃', '⊇' => '⊇', 'ß' => 'ß', 'Τ' => 'Τ', 'τ' => 'τ', '∴' => '∴', 'Θ' => 'Θ', 'θ' => 'θ', 'ϑ' => 'ϑ', ' ' => ' ', 'Þ' => 'Þ', 'þ' => 'þ', '˜' => '˜', '×' => '×', '™' => '™', 'Ú' => 'Ú', 'ú' => 'ú', '↑' => '↑', '⇑' => '⇑', 'Û' => 'Û', 'û' => 'û', 'Ù' => 'Ù', 'ù' => 'ù', '¨' => '¨', 'ϒ' => 'ϒ', 'Υ' => 'Υ', 'υ' => 'υ', 'Ü' => 'Ü', 'ü' => 'ü', '℘' => '℘', 'Ξ' => 'Ξ', 'ξ' => 'ξ', 'Ý' => 'Ý', 'ý' => 'ý', '¥' => '¥', 'ÿ' => 'ÿ', 'Ÿ' => 'Ÿ', 'Ζ' => 'Ζ', 'ζ' => 'ζ', '‍' => '‍', '‌' => '‌', '>' => '>', '<' => '<', '"' => '"', // Add apostrophe (XML). ''' => "'", ); drupal-7.26/includes/errors.inc0000644001412200141220000002412012265562324016034 0ustar benderbender array('Error', WATCHDOG_ERROR), E_WARNING => array('Warning', WATCHDOG_WARNING), E_PARSE => array('Parse error', WATCHDOG_ERROR), E_NOTICE => array('Notice', WATCHDOG_NOTICE), E_CORE_ERROR => array('Core error', WATCHDOG_ERROR), E_CORE_WARNING => array('Core warning', WATCHDOG_WARNING), E_COMPILE_ERROR => array('Compile error', WATCHDOG_ERROR), E_COMPILE_WARNING => array('Compile warning', WATCHDOG_WARNING), E_USER_ERROR => array('User error', WATCHDOG_ERROR), E_USER_WARNING => array('User warning', WATCHDOG_WARNING), E_USER_NOTICE => array('User notice', WATCHDOG_NOTICE), E_STRICT => array('Strict warning', WATCHDOG_DEBUG), E_RECOVERABLE_ERROR => array('Recoverable fatal error', WATCHDOG_ERROR), ); // E_DEPRECATED and E_USER_DEPRECATED were added in PHP 5.3.0. if (defined('E_DEPRECATED')) { $types[E_DEPRECATED] = array('Deprecated function', WATCHDOG_DEBUG); $types[E_USER_DEPRECATED] = array('User deprecated function', WATCHDOG_DEBUG); } return $types; } /** * Provides custom PHP error handling. * * @param $error_level * The level of the error raised. * @param $message * The error message. * @param $filename * The filename that the error was raised in. * @param $line * The line number the error was raised at. * @param $context * An array that points to the active symbol table at the point the error * occurred. */ function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) { if ($error_level & error_reporting()) { $types = drupal_error_levels(); list($severity_msg, $severity_level) = $types[$error_level]; $caller = _drupal_get_last_caller(debug_backtrace()); if (!function_exists('filter_xss_admin')) { require_once DRUPAL_ROOT . '/includes/common.inc'; } // We treat recoverable errors as fatal. _drupal_log_error(array( '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', // The standard PHP error handler considers that the error messages // are HTML. We mimick this behavior here. '!message' => filter_xss_admin($message), '%function' => $caller['function'], '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => $severity_level, ), $error_level == E_RECOVERABLE_ERROR); } } /** * Decodes an exception and retrieves the correct caller. * * @param $exception * The exception object that was thrown. * * @return * An error in the format expected by _drupal_log_error(). */ function _drupal_decode_exception($exception) { $message = $exception->getMessage(); $backtrace = $exception->getTrace(); // Add the line throwing the exception to the backtrace. array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile())); // For PDOException errors, we try to return the initial caller, // skipping internal functions of the database layer. if ($exception instanceof PDOException) { // The first element in the stack is the call, the second element gives us the caller. // We skip calls that occurred in one of the classes of the database layer // or in one of its global functions. $db_functions = array('db_query', 'db_query_range'); while (!empty($backtrace[1]) && ($caller = $backtrace[1]) && ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) || in_array($caller['function'], $db_functions))) { // We remove that call. array_shift($backtrace); } if (isset($exception->query_string, $exception->args)) { $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE); } } $caller = _drupal_get_last_caller($backtrace); return array( '%type' => get_class($exception), // The standard PHP exception handler considers that the exception message // is plain-text. We mimick this behavior here. '!message' => check_plain($message), '%function' => $caller['function'], '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => WATCHDOG_ERROR, ); } /** * Renders an exception error message without further exceptions. * * @param $exception * The exception object that was thrown. * @return * An error message. */ function _drupal_render_exception_safe($exception) { return check_plain(strtr('%type: !message in %function (line %line of %file).', _drupal_decode_exception($exception))); } /** * Determines whether an error should be displayed. * * When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL, * all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error * will be examined to determine if it should be displayed. * * @param $error * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME. * * @return * TRUE if an error should be displayed. */ function error_displayable($error = NULL) { $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); $updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update'); $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL); $error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME && isset($error) && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning'); return ($updating || $all_errors_displayed || $error_needs_display); } /** * Logs a PHP error or exception and displays an error page in fatal cases. * * @param $error * An array with the following keys: %type, !message, %function, %file, %line * and severity_level. All the parameters are plain-text, with the exception * of !message, which needs to be a safe HTML string. * @param $fatal * TRUE if the error is fatal. */ function _drupal_log_error($error, $fatal = FALSE) { // Initialize a maintenance theme if the bootstrap was not complete. // Do it early because drupal_set_message() triggers a drupal_theme_initialize(). if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) { unset($GLOBALS['theme']); if (!defined('MAINTENANCE_MODE')) { define('MAINTENANCE_MODE', 'error'); } drupal_maintenance_theme(); } // When running inside the testing framework, we relay the errors // to the tested site by the way of HTTP headers. $test_info = &$GLOBALS['drupal_test_info']; if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { // $number does not use drupal_static as it should not be reset // as it uniquely identifies each PHP error. static $number = 0; $assertion = array( $error['!message'], $error['%type'], array( 'function' => $error['%function'], 'file' => $error['%file'], 'line' => $error['%line'], ), ); header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion))); $number++; } watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); if ($fatal) { drupal_add_http_header('Status', '500 Service unavailable (with message)'); } if (drupal_is_cli()) { if ($fatal) { // When called from CLI, simply output a plain text message. print html_entity_decode(strip_tags(t('%type: !message in %function (line %line of %file).', $error))). "\n"; exit; } } if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { if ($fatal) { if (error_displayable($error)) { // When called from JavaScript, simply output the error message. print t('%type: !message in %function (line %line of %file).', $error); } exit; } } else { // Display the message if the current error reporting level allows this type // of message to be displayed, and unconditionnaly in update.php. if (error_displayable($error)) { $class = 'error'; // If error type is 'User notice' then treat it as debug information // instead of an error message, see dd(). if ($error['%type'] == 'User notice') { $error['%type'] = 'Debug'; $class = 'status'; } drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class); } if ($fatal) { drupal_set_title(t('Error')); // We fallback to a maintenance page at this point, because the page generation // itself can generate errors. print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.'))); exit; } } } /** * Gets the last caller from a backtrace. * * @param $backtrace * A standard PHP backtrace. * * @return * An associative array with keys 'file', 'line' and 'function'. */ function _drupal_get_last_caller($backtrace) { // Errors that occur inside PHP internal functions do not generate // information about file and line. Ignore black listed functions. $blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler'); while (($backtrace && !isset($backtrace[0]['line'])) || (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) { array_shift($backtrace); } // The first trace is the call itself. // It gives us the line and the file of the last call. $call = $backtrace[0]; // The second call give us the function where the call originated. if (isset($backtrace[1])) { if (isset($backtrace[1]['class'])) { $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()'; } else { $call['function'] = $backtrace[1]['function'] . '()'; } } else { $call['function'] = 'main()'; } return $call; } drupal-7.26/includes/update.inc0000644001412200141220000016324512265562324016016 0ustar benderbendername, $row->type)) { $incompatible[] = $row->name; } } if (!empty($incompatible)) { db_update('system') ->fields(array('status' => 0)) ->condition('name', $incompatible, 'IN') ->execute(); } } /** * Tests the compatibility of a module or theme. */ function update_check_incompatibility($name, $type = 'module') { static $themes, $modules; // Store values of expensive functions for future use. if (empty($themes) || empty($modules)) { // We need to do a full rebuild here to make sure the database reflects any // code changes that were made in the filesystem before the update script // was initiated. $themes = system_rebuild_theme_data(); $modules = system_rebuild_module_data(); } if ($type == 'module' && isset($modules[$name])) { $file = $modules[$name]; } elseif ($type == 'theme' && isset($themes[$name])) { $file = $themes[$name]; } if (!isset($file) || !isset($file->info['core']) || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY || version_compare(phpversion(), $file->info['php']) < 0) { return TRUE; } return FALSE; } /** * Performs extra steps required to bootstrap when using a Drupal 6 database. * * Users who still have a Drupal 6 database (and are in the process of * updating to Drupal 7) need extra help before a full bootstrap can be * achieved. This function does the necessary preliminary work that allows * the bootstrap to be successful. * * No access check has been performed when this function is called, so no * irreversible changes to the database are made here. */ function update_prepare_d7_bootstrap() { // Allow the bootstrap to proceed even if a Drupal 6 settings.php file is // still being used. include_once DRUPAL_ROOT . '/includes/install.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); global $databases, $db_url, $db_prefix, $update_rewrite_settings; if (empty($databases) && !empty($db_url)) { $databases = update_parse_db_url($db_url, $db_prefix); // Record the fact that the settings.php file will need to be rewritten. $update_rewrite_settings = TRUE; $settings_file = conf_path() . '/settings.php'; $writable = drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_WRITABLE); $requirements = array( 'settings file' => array( 'title' => 'Settings file', 'value' => $writable ? 'The settings file is writable.' : 'The settings file is not writable.', 'severity' => $writable ? REQUIREMENT_OK : REQUIREMENT_ERROR, 'description' => $writable ? '' : 'Drupal requires write permissions to ' . $settings_file . ' during the update process. If you are unsure how to grant file permissions, consult the online handbook.', ), ); update_extra_requirements($requirements); } // The new {blocked_ips} table is used in Drupal 7 to store a list of // banned IP addresses. If this table doesn't exist then we are still // running on a Drupal 6 database, so we suppress the unavoidable errors // that occur by creating a static list. $GLOBALS['conf']['blocked_ips'] = array(); // Check that PDO is available and that the correct PDO database driver is // loaded. Bootstrapping to DRUPAL_BOOTSTRAP_DATABASE will result in a fatal // error otherwise. $message = ''; $pdo_link = 'http://drupal.org/requirements/pdo'; // Check that PDO is loaded. if (!extension_loaded('pdo')) { $message = '

      PDO is required!

      Drupal 7 requires PHP ' . DRUPAL_MINIMUM_PHP . ' or higher with the PHP Data Objects (PDO) extension enabled.

      '; } // The PDO::ATTR_DEFAULT_FETCH_MODE constant is not available in the PECL // version of PDO. elseif (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) { $message = '

      The wrong version of PDO is installed!

      Drupal 7 requires the PHP Data Objects (PDO) extension from PHP core to be enabled. This system has the older PECL version installed.'; $pdo_link = 'http://drupal.org/requirements/pdo#pecl'; } // Check that the correct driver is loaded for the database being updated. // If we have no driver information (for example, if someone tried to create // the Drupal 7 $databases array themselves but did not do it correctly), // this message will be confusing, so do not perform the check; instead, just // let the database connection fail in the code that follows. elseif (isset($databases['default']['default']['driver']) && !in_array($databases['default']['default']['driver'], PDO::getAvailableDrivers())) { $message = '

      A PDO database driver is required!

      You need to enable the PDO_' . strtoupper($databases['default']['default']['driver']) . ' database driver for PHP ' . DRUPAL_MINIMUM_PHP . ' or higher so that Drupal 7 can access the database.

      '; } if ($message) { print $message . '

      See the system requirements page for more information.

      '; exit(); } // Allow the database system to work even if the registry has not been // created yet. drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); // If the site has not updated to Drupal 7 yet, check to make sure that it is // running an up-to-date version of Drupal 6 before proceeding. Note this has // to happen AFTER the database bootstraps because of // drupal_get_installed_schema_version(). $system_schema = drupal_get_installed_schema_version('system'); if ($system_schema < 7000) { $has_required_schema = $system_schema >= REQUIRED_D6_SCHEMA_VERSION; $requirements = array( 'drupal 6 version' => array( 'title' => 'Drupal 6 version', 'value' => $has_required_schema ? 'You are running a current version of Drupal 6.' : 'You are not running a current version of Drupal 6', 'severity' => $has_required_schema ? REQUIREMENT_OK : REQUIREMENT_ERROR, 'description' => $has_required_schema ? '' : 'Please update your Drupal 6 installation to the most recent version before attempting to upgrade to Drupal 7', ), ); // Make sure that the database environment is properly set up. try { db_run_tasks(db_driver()); } catch (DatabaseTaskException $e) { $requirements['database tasks'] = array( 'title' => 'Database environment', 'value' => 'There is a problem with your database environment', 'severity' => REQUIREMENT_ERROR, 'description' => $e->getMessage(), ); } update_extra_requirements($requirements); // Allow a D6 session to work, since the upgrade has not been performed yet. $d6_session_name = update_get_d6_session_name(); if (!empty($_COOKIE[$d6_session_name])) { // Set the current sid to the one found in the D6 cookie. $sid = $_COOKIE[$d6_session_name]; $_COOKIE[session_name()] = $sid; session_id($sid); } // Upgrading from D6 to D7.{0,1,2,3,4,8,...} is different than upgrading // from D6 to D7.{5,6,7} which should be considered broken. To be able to // properly handle this difference in node_update_7012 we need to keep track // of whether a D6 > D7 upgrade or a D7 > D7 update is running. // Since variable_set() is not available here, the D6 status is being saved // in a local variable to be able to store it later. $update_d6 = TRUE; } // Create the registry tables. if (!db_table_exists('registry')) { $schema['registry'] = array( 'fields' => array( 'name' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'type' => array('type' => 'varchar', 'length' => 9, 'not null' => TRUE, 'default' => ''), 'filename' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'module' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), ), 'primary key' => array('name', 'type'), 'indexes' => array( 'hook' => array('type', 'weight', 'module'), ), ); db_create_table('registry', $schema['registry']); } if (!db_table_exists('registry_file')) { $schema['registry_file'] = array( 'fields' => array( 'filename' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE), 'hash' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE), ), 'primary key' => array('filename'), ); db_create_table('registry_file', $schema['registry_file']); } // Older versions of Drupal 6 do not include the semaphore table, which is // required to bootstrap, so we add it now so that we can bootstrap and // provide a reasonable error message. if (!db_table_exists('semaphore')) { $semaphore = array( 'description' => 'Table for holding semaphores, locks, flags, etc. that cannot be stored as Drupal variables since they must not be cached.', 'fields' => array( 'name' => array( 'description' => 'Primary Key: Unique name.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '' ), 'value' => array( 'description' => 'A value for the semaphore.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '' ), 'expire' => array( 'description' => 'A Unix timestamp with microseconds indicating when the semaphore should expire.', 'type' => 'float', 'size' => 'big', 'not null' => TRUE ), ), 'indexes' => array( 'value' => array('value'), 'expire' => array('expire'), ), 'primary key' => array('name'), ); db_create_table('semaphore', $semaphore); } // The new cache_bootstrap bin is required to bootstrap to // DRUPAL_BOOTSTRAP_SESSION, so create it here rather than in // update_fix_d7_requirements(). if (!db_table_exists('cache_bootstrap')) { $cache_bootstrap = array( 'description' => 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.', 'fields' => array( 'cid' => array( 'description' => 'Primary Key: Unique cache ID.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', ), 'data' => array( 'description' => 'A collection of data to cache.', 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', ), 'expire' => array( 'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), 'created' => array( 'description' => 'A Unix timestamp indicating when the cache entry was created.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), 'serialized' => array( 'description' => 'A flag to indicate whether content is serialized (1) or not (0).', 'type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0, ), ), 'indexes' => array( 'expire' => array('expire'), ), 'primary key' => array('cid'), ); db_create_table('cache_bootstrap', $cache_bootstrap); } // Set a valid timezone for 6 -> 7 upgrade process. drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); $timezone_offset = variable_get('date_default_timezone', 0); if (is_numeric($timezone_offset)) { // Save the original offset. variable_set('date_temporary_timezone', $timezone_offset); // Set the timezone for this request only. $GLOBALS['conf']['date_default_timezone'] = 'UTC'; } // This allows update functions to tell if an upgrade from D6 is running. if (!empty($update_d6)) { variable_set('update_d6', TRUE); } } /** * A helper function that modules can use to assist with the transformation * from numeric block deltas to string block deltas during the 6.x -> 7.x * upgrade. * * @todo This function should be removed in 8.x. * * @param $sandbox * An array holding data for the batch process. * @param $renamed_deltas * An associative array. Keys are module names, values an associative array * mapping the old block deltas to the new block deltas for the module. * Example: * @code * $renamed_deltas = array( * 'mymodule' => * array( * 0 => 'mymodule-block-1', * 1 => 'mymodule-block-2', * ), * ); * @endcode * @param $moved_deltas * An associative array. Keys are source module names, values an associative * array mapping the (possibly renamed) block name to the new module name. * Example: * @code * $moved_deltas = array( * 'user' => * array( * 'navigation' => 'system', * ), * ); * @endcode */ function update_fix_d7_block_deltas(&$sandbox, $renamed_deltas, $moved_deltas) { // Loop through each block and make changes to the block tables. // Only run this the first time through the batch update. if (!isset($sandbox['progress'])) { // Determine whether to use the old or new block table names. $block_tables = db_table_exists('blocks') ? array('blocks', 'blocks_roles') : array('block', 'block_role'); foreach ($block_tables as $table) { foreach ($renamed_deltas as $module => $deltas) { foreach ($deltas as $old_delta => $new_delta) { // Only do the update if the old block actually exists. $block_exists = db_query("SELECT COUNT(*) FROM {" . $table . "} WHERE module = :module AND delta = :delta", array( ':module' => $module, ':delta' => $old_delta, )) ->fetchField(); if ($block_exists) { // Delete any existing blocks with the new module+delta. db_delete($table) ->condition('module', $module) ->condition('delta', $new_delta) ->execute(); // Rename the old block to the new module+delta. db_update($table) ->fields(array('delta' => $new_delta)) ->condition('module', $module) ->condition('delta', $old_delta) ->execute(); } } } foreach ($moved_deltas as $old_module => $deltas) { foreach ($deltas as $delta => $new_module) { // Only do the update if the old block actually exists. $block_exists = db_query("SELECT COUNT(*) FROM {" . $table . "} WHERE module = :module AND delta = :delta", array( ':module' => $old_module, ':delta' => $delta, )) ->fetchField(); if ($block_exists) { // Delete any existing blocks with the new module+delta. db_delete($table) ->condition('module', $new_module) ->condition('delta', $delta) ->execute(); // Rename the old block to the new module+delta. db_update($table) ->fields(array('module' => $new_module)) ->condition('module', $old_module) ->condition('delta', $delta) ->execute(); } } } } // Initialize batch update information. $sandbox['progress'] = 0; $sandbox['last_user_processed'] = -1; $sandbox['max'] = db_query("SELECT COUNT(*) FROM {users} WHERE data LIKE :block", array( ':block' => '%' . db_like(serialize('block')) . '%', )) ->fetchField(); } // Now do the batch update of the user-specific block visibility settings. $limit = 100; $result = db_select('users', 'u') ->fields('u', array('uid', 'data')) ->condition('uid', $sandbox['last_user_processed'], '>') ->condition('data', '%' . db_like(serialize('block')) . '%', 'LIKE') ->orderBy('uid', 'ASC') ->range(0, $limit) ->execute(); foreach ($result as $row) { $data = unserialize($row->data); $user_needs_update = FALSE; foreach ($renamed_deltas as $module => $deltas) { foreach ($deltas as $old_delta => $new_delta) { if (isset($data['block'][$module][$old_delta])) { // Transfer the old block visibility settings to the newly-renamed // block, and mark this user for a database update. $data['block'][$module][$new_delta] = $data['block'][$module][$old_delta]; unset($data['block'][$module][$old_delta]); $user_needs_update = TRUE; } } } foreach ($moved_deltas as $old_module => $deltas) { foreach ($deltas as $delta => $new_module) { if (isset($data['block'][$old_module][$delta])) { // Transfer the old block visibility settings to the moved // block, and mark this user for a database update. $data['block'][$new_module][$delta] = $data['block'][$old_module][$delta]; unset($data['block'][$old_module][$delta]); $user_needs_update = TRUE; } } } // Update the current user. if ($user_needs_update) { db_update('users') ->fields(array('data' => serialize($data))) ->condition('uid', $row->uid) ->execute(); } // Update our progress information for the batch update. $sandbox['progress']++; $sandbox['last_user_processed'] = $row->uid; } // Indicate our current progress to the batch update system. if ($sandbox['progress'] < $sandbox['max']) { $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max']; } } /** * Perform Drupal 6.x to 7.x updates that are required for update.php * to function properly. * * This function runs when update.php is run the first time for 7.x, * even before updates are selected or performed. It is important * that if updates are not ultimately performed that no changes are * made which make it impossible to continue using the prior version. */ function update_fix_d7_requirements() { global $conf; // Rewrite the settings.php file if necessary, see // update_prepare_d7_bootstrap(). global $update_rewrite_settings, $db_url, $db_prefix; if (!empty($update_rewrite_settings)) { $databases = update_parse_db_url($db_url, $db_prefix); $salt = drupal_hash_base64(drupal_random_bytes(55)); file_put_contents(conf_path() . '/settings.php', "\n" . '$databases = ' . var_export($databases, TRUE) . ";\n\$drupal_hash_salt = '$salt';", FILE_APPEND); } if (drupal_get_installed_schema_version('system') < 7000 && !variable_get('update_d7_requirements', FALSE)) { // Change 6.x system table field values to 7.x equivalent. // Change field values. db_change_field('system', 'schema_version', 'schema_version', array( 'type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => -1) ); db_change_field('system', 'status', 'status', array( 'type' => 'int', 'not null' => TRUE, 'default' => 0)); db_change_field('system', 'weight', 'weight', array( 'type' => 'int', 'not null' => TRUE, 'default' => 0)); db_change_field('system', 'bootstrap', 'bootstrap', array( 'type' => 'int', 'not null' => TRUE, 'default' => 0)); // Drop and recreate 6.x indexes. db_drop_index('system', 'bootstrap'); db_add_index('system', 'bootstrap', array( 'status', 'bootstrap', array('type', 12), 'weight', 'name')); db_drop_index('system' ,'modules'); db_add_index('system', 'modules', array(array( 'type', 12), 'status', 'weight', 'name')); db_drop_index('system', 'type_name'); db_add_index('system', 'type_name', array(array('type', 12), 'name')); // Add 7.x indexes. db_add_index('system', 'system_list', array('weight', 'name')); // Add the cache_path table. if (db_table_exists('cache_path')) { db_drop_table('cache_path'); } require_once('./modules/system/system.install'); $schema['cache_path'] = system_schema_cache_7054(); $schema['cache_path']['description'] = 'Cache table used for path alias lookups.'; db_create_table('cache_path', $schema['cache_path']); // system_update_7042() renames columns, but these are needed to bootstrap. // Add empty columns for now. db_add_field('url_alias', 'source', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); db_add_field('url_alias', 'alias', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); // Add new columns to {menu_router}. db_add_field('menu_router', 'delivery_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); db_add_field('menu_router', 'context', array( 'description' => 'Only for local tasks (tabs) - the context of a local task to control its placement.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, )); db_drop_index('menu_router', 'tab_parent'); db_add_index('menu_router', 'tab_parent', array(array('tab_parent', 64), 'weight', 'title')); db_add_field('menu_router', 'theme_callback', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); db_add_field('menu_router', 'theme_arguments', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); // Add the role_permission table. $schema['role_permission'] = array( 'fields' => array( 'rid' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, ), 'permission' => array( 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => '', ), ), 'primary key' => array('rid', 'permission'), 'indexes' => array( 'permission' => array('permission'), ), ); db_create_table('role_permission', $schema['role_permission']); // Drops and recreates semaphore value index. db_drop_index('semaphore', 'value'); db_add_index('semaphore', 'value', array('value')); $schema['date_format_type'] = array( 'description' => 'Stores configured date format types.', 'fields' => array( 'type' => array( 'description' => 'The date format type, e.g. medium.', 'type' => 'varchar', 'length' => 64, 'not null' => TRUE, ), 'title' => array( 'description' => 'The human readable name of the format type.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'locked' => array( 'description' => 'Whether or not this is a system provided format.', 'type' => 'int', 'size' => 'tiny', 'default' => 0, 'not null' => TRUE, ), ), 'primary key' => array('type'), ); $schema['date_formats'] = array( 'description' => 'Stores configured date formats.', 'fields' => array( 'dfid' => array( 'description' => 'The date format identifier.', 'type' => 'serial', 'not null' => TRUE, 'unsigned' => TRUE, ), 'format' => array( 'description' => 'The date format string.', 'type' => 'varchar', 'length' => 100, 'not null' => TRUE, ), 'type' => array( 'description' => 'The date format type, e.g. medium.', 'type' => 'varchar', 'length' => 64, 'not null' => TRUE, ), 'locked' => array( 'description' => 'Whether or not this format can be modified.', 'type' => 'int', 'size' => 'tiny', 'default' => 0, 'not null' => TRUE, ), ), 'primary key' => array('dfid'), 'unique keys' => array('formats' => array('format', 'type')), ); $schema['date_format_locale'] = array( 'description' => 'Stores configured date formats for each locale.', 'fields' => array( 'format' => array( 'description' => 'The date format string.', 'type' => 'varchar', 'length' => 100, 'not null' => TRUE, ), 'type' => array( 'description' => 'The date format type, e.g. medium.', 'type' => 'varchar', 'length' => 64, 'not null' => TRUE, ), 'language' => array( 'description' => 'A {languages}.language for this format to be used with.', 'type' => 'varchar', 'length' => 12, 'not null' => TRUE, ), ), 'primary key' => array('type', 'language'), ); db_create_table('date_format_type', $schema['date_format_type']); // Sites that have the Drupal 6 Date module installed already have the // following tables. if (db_table_exists('date_formats')) { db_rename_table('date_formats', 'd6_date_formats'); } db_create_table('date_formats', $schema['date_formats']); if (db_table_exists('date_format_locale')) { db_rename_table('date_format_locale', 'd6_date_format_locale'); } db_create_table('date_format_locale', $schema['date_format_locale']); // Add the queue table. $schema['queue'] = array( 'description' => 'Stores items in queues.', 'fields' => array( 'item_id' => array( 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Primary Key: Unique item ID.', ), 'name' => array( 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The queue name.', ), 'data' => array( 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', 'serialize' => TRUE, 'description' => 'The arbitrary data for the item.', ), 'expire' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => 'Timestamp when the claim lease expires on the item.', ), 'created' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => 'Timestamp when the item was created.', ), ), 'primary key' => array('item_id'), 'indexes' => array( 'name_created' => array('name', 'created'), 'expire' => array('expire'), ), ); // Check for queue table that may remain from D5 or D6, if found //drop it. if (db_table_exists('queue')) { db_drop_table('queue'); } db_create_table('queue', $schema['queue']); // Create the sequences table. $schema['sequences'] = array( 'description' => 'Stores IDs.', 'fields' => array( 'value' => array( 'description' => 'The value of the sequence.', 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, ), ), 'primary key' => array('value'), ); // Check for sequences table that may remain from D5 or D6, if found //drop it. if (db_table_exists('sequences')) { db_drop_table('sequences'); } db_create_table('sequences', $schema['sequences']); // Initialize the table with the maximum current increment of the tables // that will rely on it for their ids. $max_aid = db_query('SELECT MAX(aid) FROM {actions_aid}')->fetchField(); $max_uid = db_query('SELECT MAX(uid) FROM {users}')->fetchField(); $max_batch_id = db_query('SELECT MAX(bid) FROM {batch}')->fetchField(); db_insert('sequences')->fields(array('value' => max($max_aid, $max_uid, $max_batch_id)))->execute(); // Add column for locale context. if (db_table_exists('locales_source')) { db_add_field('locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The context this string applies to.')); } // Rename 'site_offline_message' variable to 'maintenance_mode_message' // and 'site_offline' variable to 'maintenance_mode'. // Old variable is removed in update for system.module, see // system_update_7072(). if ($message = variable_get('site_offline_message', NULL)) { variable_set('maintenance_mode_message', $message); } // Old variable is removed in update for system.module, see // system_update_7069(). $site_offline = variable_get('site_offline', -1); if ($site_offline != -1) { variable_set('maintenance_mode', $site_offline); } // Add ssid column and index. db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by Drupal's session handlers.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => '')); db_add_index('sessions', 'ssid', array('ssid')); // Drop existing primary key. db_drop_primary_key('sessions'); // Add new primary key. db_add_primary_key('sessions', array('sid', 'ssid')); // Allow longer javascript file names. if (db_table_exists('languages')) { db_change_field('languages', 'javascript', 'javascript', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')); } // Rename action description to label. db_change_field('actions', 'description', 'label', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '0')); variable_set('update_d7_requirements', TRUE); } update_fix_d7_install_profile(); } /** * Register the currently installed profile in the system table. * * Installation profiles are now treated as modules by Drupal, and have an * upgrade path based on their schema version in the system table. * * The installation profile will be set to schema_version 0, as it has already * been installed. Any other hook_update_N functions provided by the * installation profile will be run by update.php. */ function update_fix_d7_install_profile() { $profile = drupal_get_profile(); $results = db_select('system', 's') ->fields('s', array('name', 'schema_version')) ->condition('name', $profile) ->condition('type', 'module') ->execute() ->fetchAll(); if (empty($results)) { $filename = 'profiles/' . $profile . '/' . $profile . '.profile'; // Read profile info file $info = drupal_parse_info_file(dirname($filename) . '/' . $profile . '.info'); // Merge in defaults. $info = $info + array( 'dependencies' => array(), 'description' => '', 'package' => 'Other', 'version' => NULL, 'php' => DRUPAL_MINIMUM_PHP, 'files' => array(), ); $values = array( 'filename' => $filename, 'name' => $profile, 'info' => serialize($info), 'schema_version' => 0, 'type' => 'module', 'status' => 1, 'owner' => '', ); // Installation profile hooks are always executed last by the module system $values['weight'] = 1000; // Initializing the system table entry for the installation profile db_insert('system') ->fields(array_keys($values)) ->values($values) ->execute(); // Reset the cached schema version. drupal_get_installed_schema_version($profile, TRUE); // Load the updates again to make sure the installation profile updates // are loaded. drupal_load_updates(); } } /** * Parse pre-Drupal 7 database connection URLs and return D7 compatible array. * * @return * Drupal 7 DBTNG compatible array of database connection information. */ function update_parse_db_url($db_url, $db_prefix) { $databases = array(); if (!is_array($db_url)) { $db_url = array('default' => $db_url); } foreach ($db_url as $database => $url) { $url = parse_url($url); $databases[$database]['default'] = array( // MySQLi uses the mysql driver. 'driver' => $url['scheme'] == 'mysqli' ? 'mysql' : $url['scheme'], // Remove the leading slash to get the database name. 'database' => substr(urldecode($url['path']), 1), 'username' => urldecode($url['user']), 'password' => isset($url['pass']) ? urldecode($url['pass']) : '', 'host' => urldecode($url['host']), 'port' => isset($url['port']) ? urldecode($url['port']) : '', ); if (isset($db_prefix)) { $databases[$database]['default']['prefix'] = $db_prefix; } } return $databases; } /** * Constructs a session name compatible with a D6 environment. * * @return * D6-compatible session name string. * * @see drupal_settings_initialize() */ function update_get_d6_session_name() { global $base_url, $cookie_domain; $cookie_secure = ini_get('session.cookie_secure'); // If a custom cookie domain is set in settings.php, that variable forms // the basis of the session name. Re-compute the D7 hashing method to find // out if $cookie_domain was used as the session name. if (($cookie_secure ? 'SSESS' : 'SESS') . substr(hash('sha256', $cookie_domain), 0, 32) == session_name()) { $session_name = $cookie_domain; } else { // Otherwise use $base_url as session name, without the protocol // to use the same session identifiers across HTTP and HTTPS. list( , $session_name) = explode('://', $base_url, 2); } if ($cookie_secure) { $session_name .= 'SSL'; } return 'SESS' . md5($session_name); } /** * Performs one update and stores the results for display on the results page. * * If an update function completes successfully, it should return a message * as a string indicating success, for example: * @code * return t('New index added successfully.'); * @endcode * * Alternatively, it may return nothing. In that case, no message * will be displayed at all. * * If it fails for whatever reason, it should throw an instance of * DrupalUpdateException with an appropriate error message, for example: * @code * throw new DrupalUpdateException(t('Description of what went wrong')); * @endcode * * If an exception is thrown, the current update and all updates that depend on * it will be aborted. The schema version will not be updated in this case, and * all the aborted updates will continue to appear on update.php as updates * that have not yet been run. * * If an update function needs to be re-run as part of a batch process, it * should accept the $sandbox array by reference as its first parameter * and set the #finished property to the percentage completed that it is, as a * fraction of 1. * * @param $module * The module whose update will be run. * @param $number * The update number to run. * @param $dependency_map * An array whose keys are the names of all update functions that will be * performed during this batch process, and whose values are arrays of other * update functions that each one depends on. * @param $context * The batch context array. * * @see update_resolve_dependencies() */ function update_do_one($module, $number, $dependency_map, &$context) { $function = $module . '_update_' . $number; // If this update was aborted in a previous step, or has a dependency that // was aborted in a previous step, go no further. if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { return; } $ret = array(); if (function_exists($function)) { try { $ret['results']['query'] = $function($context['sandbox']); $ret['results']['success'] = TRUE; } // @TODO We may want to do different error handling for different // exception types, but for now we'll just log the exception and // return the message for printing. catch (Exception $e) { watchdog_exception('update', $e); require_once DRUPAL_ROOT . '/includes/errors.inc'; $variables = _drupal_decode_exception($e); // The exception message is run through check_plain() by _drupal_decode_exception(). $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables)); } } if (isset($context['sandbox']['#finished'])) { $context['finished'] = $context['sandbox']['#finished']; unset($context['sandbox']['#finished']); } if (!isset($context['results'][$module])) { $context['results'][$module] = array(); } if (!isset($context['results'][$module][$number])) { $context['results'][$module][$number] = array(); } $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); if (!empty($ret['#abort'])) { // Record this function in the list of updates that were aborted. $context['results']['#abort'][] = $function; } // Record the schema update if it was completed successfully. if ($context['finished'] == 1 && empty($ret['#abort'])) { drupal_set_installed_schema_version($module, $number); } $context['message'] = 'Updating ' . check_plain($module) . ' module'; } /** * @class Exception class used to throw error if a module update fails. */ class DrupalUpdateException extends Exception { } /** * Starts the database update batch process. * * @param $start * An array whose keys contain the names of modules to be updated during the * current batch process, and whose values contain the number of the first * requested update for that module. The actual updates that are run (and the * order they are run in) will depend on the results of passing this data * through the update dependency system. * @param $redirect * Path to redirect to when the batch has finished processing. * @param $url * URL of the batch processing page (should only be used for separate * scripts like update.php). * @param $batch * Optional parameters to pass into the batch API. * @param $redirect_callback * (optional) Specify a function to be called to redirect to the progressive * processing page. * * @see update_resolve_dependencies() */ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = 'drupal_goto') { // During the update, bring the site offline so that schema changes do not // affect visiting users. $_SESSION['maintenance_mode'] = variable_get('maintenance_mode', FALSE); if ($_SESSION['maintenance_mode'] == FALSE) { variable_set('maintenance_mode', TRUE); } // Resolve any update dependencies to determine the actual updates that will // be run and the order they will be run in. $updates = update_resolve_dependencies($start); // Store the dependencies for each update function in an array which the // batch API can pass in to the batch operation each time it is called. (We // do not store the entire update dependency array here because it is // potentially very large.) $dependency_map = array(); foreach ($updates as $function => $update) { $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); } $operations = array(); foreach ($updates as $update) { if ($update['allowed']) { // Set the installed version of each module so updates will start at the // correct place. (The updates are already sorted, so we can simply base // this on the first one we come across in the above foreach loop.) if (isset($start[$update['module']])) { drupal_set_installed_schema_version($update['module'], $update['number'] - 1); unset($start[$update['module']]); } // Add this update function to the batch. $function = $update['module'] . '_update_' . $update['number']; $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); } } $batch['operations'] = $operations; $batch += array( 'title' => 'Updating', 'init_message' => 'Starting updates', 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', 'finished' => 'update_finished', 'file' => 'includes/update.inc', ); batch_set($batch); batch_process($redirect, $url, $redirect_callback); } /** * Finishes the update process and stores the results for eventual display. * * After the updates run, all caches are flushed. The update results are * stored into the session (for example, to be displayed on the update results * page in update.php). Additionally, if the site was off-line, now that the * update process is completed, the site is set back online. * * @param $success * Indicate that the batch API tasks were all completed successfully. * @param $results * An array of all the results that were updated in update_do_one(). * @param $operations * A list of all the operations that had not been completed by the batch API. * * @see update_batch() */ function update_finished($success, $results, $operations) { // Remove the D6 upgrade flag variable so that subsequent update runs do not // get the wrong context. variable_del('update_d6'); // Clear the caches in case the data has been updated. drupal_flush_all_caches(); $_SESSION['update_results'] = $results; $_SESSION['update_success'] = $success; $_SESSION['updates_remaining'] = $operations; // Now that the update is done, we can put the site back online if it was // previously in maintenance mode. if (isset($_SESSION['maintenance_mode']) && $_SESSION['maintenance_mode'] == FALSE) { variable_set('maintenance_mode', FALSE); unset($_SESSION['maintenance_mode']); } } /** * Returns a list of all the pending database updates. * * @return * An associative array keyed by module name which contains all information * about database updates that need to be run, and any updates that are not * going to proceed due to missing requirements. The system module will * always be listed first. * * The subarray for each module can contain the following keys: * - start: The starting update that is to be processed. If this does not * exist then do not process any updates for this module as there are * other requirements that need to be resolved. * - warning: Any warnings about why this module can not be updated. * - pending: An array of all the pending updates for the module including * the update number and the description from source code comment for * each update function. This array is keyed by the update number. */ function update_get_update_list() { // Make sure that the system module is first in the list of updates. $ret = array('system' => array()); $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE); foreach ($modules as $module => $schema_version) { // Skip uninstalled and incompatible modules. if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) { continue; } // Otherwise, get the list of updates defined by this module. $updates = drupal_get_schema_versions($module); if ($updates !== FALSE) { // module_invoke returns NULL for nonexisting hooks, so if no updates // are removed, it will == 0. $last_removed = module_invoke($module, 'update_last_removed'); if ($schema_version < $last_removed) { $ret[$module]['warning'] = '' . $module . ' module can not be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update ' . $module . ' module, you will first need to upgrade to the last version in which these updates were available.'; continue; } $updates = drupal_map_assoc($updates); foreach (array_keys($updates) as $update) { if ($update > $schema_version) { // The description for an update comes from its Doxygen. $func = new ReflectionFunction($module . '_update_' . $update); $description = str_replace(array("\n", '*', '/'), '', $func->getDocComment()); $ret[$module]['pending'][$update] = "$update - $description"; if (!isset($ret[$module]['start'])) { $ret[$module]['start'] = $update; } } } if (!isset($ret[$module]['start']) && isset($ret[$module]['pending'])) { $ret[$module]['start'] = $schema_version; } } } if (empty($ret['system'])) { unset($ret['system']); } return $ret; } /** * Resolves dependencies in a set of module updates, and orders them correctly. * * This function receives a list of requested module updates and determines an * appropriate order to run them in such that all update dependencies are met. * Any updates whose dependencies cannot be met are included in the returned * array but have the key 'allowed' set to FALSE; the calling function should * take responsibility for ensuring that these updates are ultimately not * performed. * * In addition, the returned array also includes detailed information about the * dependency chain for each update, as provided by the depth-first search * algorithm in drupal_depth_first_search(). * * @param $starting_updates * An array whose keys contain the names of modules with updates to be run * and whose values contain the number of the first requested update for that * module. * * @return * An array whose keys are the names of all update functions within the * provided modules that would need to be run in order to fulfill the * request, arranged in the order in which the update functions should be * run. (This includes the provided starting update for each module and all * subsequent updates that are available.) The values are themselves arrays * containing all the keys provided by the drupal_depth_first_search() * algorithm, which encode detailed information about the dependency chain * for this update function (for example: 'paths', 'reverse_paths', 'weight', * and 'component'), as well as the following additional keys: * - 'allowed': A boolean which is TRUE when the update function's * dependencies are met, and FALSE otherwise. Calling functions should * inspect this value before running the update. * - 'missing_dependencies': An array containing the names of any other * update functions that are required by this one but that are unavailable * to be run. This array will be empty when 'allowed' is TRUE. * - 'module': The name of the module that this update function belongs to. * - 'number': The number of this update function within that module. * * @see drupal_depth_first_search() */ function update_resolve_dependencies($starting_updates) { // Obtain a dependency graph for the requested update functions. $update_functions = update_get_update_function_list($starting_updates); $graph = update_build_dependency_graph($update_functions); // Perform the depth-first search and sort the results. require_once DRUPAL_ROOT . '/includes/graph.inc'; drupal_depth_first_search($graph); uasort($graph, 'drupal_sort_weight'); foreach ($graph as $function => &$data) { $module = $data['module']; $number = $data['number']; // If the update function is missing and has not yet been performed, mark // it and everything that ultimately depends on it as disallowed. if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) { $data['allowed'] = FALSE; foreach (array_keys($data['paths']) as $dependent) { $graph[$dependent]['allowed'] = FALSE; $graph[$dependent]['missing_dependencies'][] = $function; } } elseif (!isset($data['allowed'])) { $data['allowed'] = TRUE; $data['missing_dependencies'] = array(); } // Now that we have finished processing this function, remove it from the // graph if it was not part of the original list. This ensures that we // never try to run any updates that were not specifically requested. if (!isset($update_functions[$module][$number])) { unset($graph[$function]); } } return $graph; } /** * Returns an organized list of update functions for a set of modules. * * @param $starting_updates * An array whose keys contain the names of modules and whose values contain * the number of the first requested update for that module. * * @return * An array containing all the update functions that should be run for each * module, including the provided starting update and all subsequent updates * that are available. The keys of the array contain the module names, and * each value is an ordered array of update functions, keyed by the update * number. * * @see update_resolve_dependencies() */ function update_get_update_function_list($starting_updates) { // Go through each module and find all updates that we need (including the // first update that was requested and any updates that run after it). $update_functions = array(); foreach ($starting_updates as $module => $version) { $update_functions[$module] = array(); $updates = drupal_get_schema_versions($module); if ($updates !== FALSE) { $max_version = max($updates); if ($version <= $max_version) { foreach ($updates as $update) { if ($update >= $version) { $update_functions[$module][$update] = $module . '_update_' . $update; } } } } } return $update_functions; } /** * Constructs a graph which encodes the dependencies between module updates. * * This function returns an associative array which contains a "directed graph" * representation of the dependencies between a provided list of update * functions, as well as any outside update functions that they directly depend * on but that were not in the provided list. The vertices of the graph * represent the update functions themselves, and each edge represents a * requirement that the first update function needs to run before the second. * For example, consider this graph: * * system_update_7000 ---> system_update_7001 ---> system_update_7002 * * Visually, this indicates that system_update_7000() must run before * system_update_7001(), which in turn must run before system_update_7002(). * * The function takes into account standard dependencies within each module, as * shown above (i.e., the fact that each module's updates must run in numerical * order), but also finds any cross-module dependencies that are defined by * modules which implement hook_update_dependencies(), and builds them into the * graph as well. * * @param $update_functions * An organized array of update functions, in the format returned by * update_get_update_function_list(). * * @return * A multidimensional array representing the dependency graph, suitable for * passing in to drupal_depth_first_search(), but with extra information * about each update function also included. Each array key contains the name * of an update function, including all update functions from the provided * list as well as any outside update functions which they directly depend * on. Each value is an associative array containing the following keys: * - 'edges': A representation of any other update functions that immediately * depend on this one. See drupal_depth_first_search() for more details on * the format. * - 'module': The name of the module that this update function belongs to. * - 'number': The number of this update function within that module. * * @see drupal_depth_first_search() * @see update_resolve_dependencies() */ function update_build_dependency_graph($update_functions) { // Initialize an array that will define a directed graph representing the // dependencies between update functions. $graph = array(); // Go through each update function and build an initial list of dependencies. foreach ($update_functions as $module => $functions) { $previous_function = NULL; foreach ($functions as $number => $function) { // Add an edge to the directed graph representing the fact that each // update function in a given module must run after the update that // numerically precedes it. if ($previous_function) { $graph[$previous_function]['edges'][$function] = TRUE; } $previous_function = $function; // Define the module and update number associated with this function. $graph[$function]['module'] = $module; $graph[$function]['number'] = $number; } } // Now add any explicit update dependencies declared by modules. $update_dependencies = update_retrieve_dependencies(); foreach ($graph as $function => $data) { if (!empty($update_dependencies[$data['module']][$data['number']])) { foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) { $dependency = $module . '_update_' . $number; $graph[$dependency]['edges'][$function] = TRUE; $graph[$dependency]['module'] = $module; $graph[$dependency]['number'] = $number; } } } return $graph; } /** * Determines if a module update is missing or unavailable. * * @param $module * The name of the module. * @param $number * The number of the update within that module. * @param $update_functions * An organized array of update functions, in the format returned by * update_get_update_function_list(). This should represent all module * updates that are requested to run at the time this function is called. * * @return * TRUE if the provided module update is not installed or is not in the * provided list of updates to run; FALSE otherwise. */ function update_is_missing($module, $number, $update_functions) { return !isset($update_functions[$module][$number]) || !function_exists($update_functions[$module][$number]); } /** * Determines if a module update has already been performed. * * @param $module * The name of the module. * @param $number * The number of the update within that module. * * @return * TRUE if the database schema indicates that the update has already been * performed; FALSE otherwise. */ function update_already_performed($module, $number) { return $number <= drupal_get_installed_schema_version($module); } /** * Invokes hook_update_dependencies() in all installed modules. * * This function is similar to module_invoke_all(), with the main difference * that it does not require that a module be enabled to invoke its hook, only * that it be installed. This allows the update system to properly perform * updates even on modules that are currently disabled. * * @return * An array of return values obtained by merging the results of the * hook_update_dependencies() implementations in all installed modules. * * @see module_invoke_all() * @see hook_update_dependencies() */ function update_retrieve_dependencies() { $return = array(); // Get a list of installed modules, arranged so that we invoke their hooks in // the same order that module_invoke_all() does. $modules = db_query("SELECT name FROM {system} WHERE type = 'module' AND schema_version <> :schema ORDER BY weight ASC, name ASC", array(':schema' => SCHEMA_UNINSTALLED))->fetchCol(); foreach ($modules as $module) { $function = $module . '_update_dependencies'; if (function_exists($function)) { $result = $function(); // Each implementation of hook_update_dependencies() returns a // multidimensional, associative array containing some keys that // represent module names (which are strings) and other keys that // represent update function numbers (which are integers). We cannot use // array_merge_recursive() to properly merge these results, since it // treats strings and integers differently. Therefore, we have to // explicitly loop through the expected array structure here and perform // the merge manually. if (isset($result) && is_array($result)) { foreach ($result as $module => $module_data) { foreach ($module_data as $update => $update_data) { foreach ($update_data as $module_dependency => $update_dependency) { // If there are redundant dependencies declared for the same // update function (so that it is declared to depend on more than // one update from a particular module), record the dependency on // the highest numbered update here, since that automatically // implies the previous ones. For example, if one module's // implementation of hook_update_dependencies() required this // ordering: // // system_update_7001 ---> user_update_7000 // // but another module's implementation of the hook required this // one: // // system_update_7002 ---> user_update_7000 // // we record the second one, since system_update_7001() is always // guaranteed to run before system_update_7002() anyway (within // an individual module, updates are always run in numerical // order). if (!isset($return[$module][$update][$module_dependency]) || $update_dependency > $return[$module][$update][$module_dependency]) { $return[$module][$update][$module_dependency] = $update_dependency; } } } } } } } return $return; } drupal-7.26/includes/language.inc0000644001412200141220000004601412265562324016311 0ustar benderbenderlangcode; * } * return $langcode; * } * @endcode * * For more information, see * @link http://drupal.org/node/1497272 Language Negotiation API @endlink */ /** * Returns all the defined language types. * * @return * An array of language type names. The name will be used as the global * variable name the language value will be stored in. */ function language_types_info() { $language_types = &drupal_static(__FUNCTION__); if (!isset($language_types)) { $language_types = module_invoke_all('language_types_info'); // Let other modules alter the list of language types. drupal_alter('language_types_info', $language_types); } return $language_types; } /** * Returns only the configurable language types. * * A language type maybe configurable or fixed. A fixed language type is a type * whose language negotiation providers are module-defined and not altered * through the user interface. * * @param $stored * Optional. By default retrieves values from the 'language_types' variable to * avoid unnecessary hook invocations. * If set to FALSE retrieves values from the actual language type definitions. * This allows to react to alterations performed on the definitions by modules * installed after the 'language_types' variable is set. * * @return * An array of language type names. */ function language_types_configurable($stored = TRUE) { $configurable = &drupal_static(__FUNCTION__); if ($stored && !isset($configurable)) { $types = variable_get('language_types', drupal_language_types()); $configurable = array_keys(array_filter($types)); } if (!$stored) { $result = array(); foreach (language_types_info() as $type => $info) { if (!isset($info['fixed'])) { $result[] = $type; } } return $result; } return $configurable; } /** * Disables the given language types. * * @param $types * An array of language types. */ function language_types_disable($types) { $enabled_types = variable_get('language_types', drupal_language_types()); foreach ($types as $type) { unset($enabled_types[$type]); } variable_set('language_types', $enabled_types); } /** * Updates the language type configuration. */ function language_types_set() { // Ensure that we are getting the defined language negotiation information. An // invocation of module_enable() or module_disable() could outdate the cached // information. drupal_static_reset('language_types_info'); drupal_static_reset('language_negotiation_info'); // Determine which language types are configurable and which not by checking // whether the 'fixed' key is defined. Non-configurable (fixed) language types // have their language negotiation settings stored there. $defined_providers = language_negotiation_info(); foreach (language_types_info() as $type => $info) { if (isset($info['fixed'])) { $language_types[$type] = FALSE; $negotiation = array(); foreach ($info['fixed'] as $weight => $id) { if (isset($defined_providers[$id])) { $negotiation[$id] = $weight; } } language_negotiation_set($type, $negotiation); } else { $language_types[$type] = TRUE; } } // Save language types. variable_set('language_types', $language_types); // Ensure that subsequent calls of language_types_configurable() return the // updated language type information. drupal_static_reset('language_types_configurable'); } /** * Checks whether a language negotiation provider is enabled for a language type. * * This has two possible behaviors: * - If $provider_id is given return its ID if enabled, FALSE otherwise. * - If no ID is passed the first enabled language negotiation provider is * returned. * * @param $type * The language negotiation provider type. * @param $provider_id * The language negotiation provider ID. * * @return * The provider ID if it is enabled, FALSE otherwise. */ function language_negotiation_get($type, $provider_id = NULL) { $negotiation = variable_get("language_negotiation_$type", array()); if (empty($negotiation)) { return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE; } if (empty($provider_id)) { return key($negotiation); } if (isset($negotiation[$provider_id])) { return $provider_id; } return FALSE; } /** * Checks if the language negotiation provider is enabled for any language type. * * @param $provider_id * The language negotiation provider ID. * * @return * TRUE if there is at least one language type for which the given language * provider is enabled, FALSE otherwise. */ function language_negotiation_get_any($provider_id) { foreach (language_types_configurable() as $type) { if (language_negotiation_get($type, $provider_id)) { return TRUE; } } return FALSE; } /** * Returns the language switch links for the given language. * * @param $type * The language negotiation type. * @param $path * The internal path the switch links will be relative to. * * @return * A keyed array of links ready to be themed. */ function language_negotiation_get_switch_links($type, $path) { $links = FALSE; $negotiation = variable_get("language_negotiation_$type", array()); // Only get the languages if we have more than one. if (count(language_list()) >= 2) { $language = language_initialize($type); } foreach ($negotiation as $id => $provider) { if (isset($provider['callbacks']['switcher'])) { if (isset($provider['file'])) { require_once DRUPAL_ROOT . '/' . $provider['file']; } $callback = $provider['callbacks']['switcher']; $result = $callback($type, $path); // Add support for WCAG 2.0's Language of Parts to add language identifiers. // http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html foreach ($result as $langcode => $link) { $result[$langcode]['attributes']['lang'] = $langcode; } if (!empty($result)) { // Allow modules to provide translations for specific links. drupal_alter('language_switch_links', $result, $type, $path); $links = (object) array('links' => $result, 'provider' => $id); break; } } } return $links; } /** * Removes any unused language negotiation providers from the configuration. */ function language_negotiation_purge() { // Ensure that we are getting the defined language negotiation information. An // invocation of module_enable() or module_disable() could outdate the cached // information. drupal_static_reset('language_negotiation_info'); drupal_static_reset('language_types_info'); $defined_providers = language_negotiation_info(); foreach (language_types_info() as $type => $type_info) { $weight = 0; $negotiation = array(); foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) { if (isset($defined_providers[$id])) { $negotiation[$id] = $weight++; } } language_negotiation_set($type, $negotiation); } } /** * Saves a list of language negotiation providers. * * @param $type * The language negotiation type. * @param $language_providers * An array of language negotiation provider weights keyed by provider ID. * @see language_provider_weight() */ function language_negotiation_set($type, $language_providers) { // Save only the necessary fields. $provider_fields = array('callbacks', 'file', 'cache'); $negotiation = array(); $providers_weight = array(); $defined_providers = language_negotiation_info(); $default_types = language_types_configurable(FALSE); // Initialize the providers weight list. foreach ($language_providers as $id => $provider) { $providers_weight[$id] = language_provider_weight($provider); } // Order providers list by weight. asort($providers_weight); foreach ($providers_weight as $id => $weight) { if (isset($defined_providers[$id])) { $provider = $defined_providers[$id]; // If the provider does not express any preference about types, make it // available for any configurable type. $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types); // Check whether the provider is defined and has the right type. if (isset($types[$type])) { $provider_data = array(); foreach ($provider_fields as $field) { if (isset($provider[$field])) { $provider_data[$field] = $provider[$field]; } } $negotiation[$id] = $provider_data; } } } variable_set("language_negotiation_$type", $negotiation); } /** * Returns all the defined language negotiation providers. * * @return * An array of language negotiation providers. */ function language_negotiation_info() { $language_providers = &drupal_static(__FUNCTION__); if (!isset($language_providers)) { // Collect all the module-defined language negotiation providers. $language_providers = module_invoke_all('language_negotiation_info'); // Add the default language negotiation provider. $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( 'callbacks' => array('language' => 'language_from_default'), 'weight' => 10, 'name' => t('Default'), 'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)), ); // Let other modules alter the list of language negotiation providers. drupal_alter('language_negotiation_info', $language_providers); } return $language_providers; } /** * Helper function used to cache the language negotiation providers results. * * @param $provider_id * The language negotiation provider's identifier. * @param $provider * (optional) An associative array of information about the provider to be * invoked (see hook_language_negotiation_info() for details). If not passed * in, it will be loaded through language_negotiation_info(). * * @return * A language object representing the language chosen by the provider. */ function language_provider_invoke($provider_id, $provider = NULL) { $results = &drupal_static(__FUNCTION__); if (!isset($results[$provider_id])) { global $user; // Get languages grouped by status and select only the enabled ones. $languages = language_list('enabled'); $languages = $languages[1]; if (!isset($provider)) { $providers = language_negotiation_info(); $provider = $providers[$provider_id]; } if (isset($provider['file'])) { require_once DRUPAL_ROOT . '/' . $provider['file']; } // If the language negotiation provider has no cache preference or this is // satisfied we can execute the callback. $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0); $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE; $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE; $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; } // Since objects are resources, we need to return a clone to prevent the // language negotiation provider cache from being unintentionally altered. The // same providers might be used with different language types based on // configuration. return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id]; } /** * Returns the passed language negotiation provider weight or a default value. * * @param $provider * A language negotiation provider data structure. * * @return * A numeric weight. */ function language_provider_weight($provider) { $default = is_numeric($provider) ? $provider : 0; return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default; } /** * Chooses a language based on language negotiation provider settings. * * @param $type * The language type key to find the language for. * * @return * The negotiated language object. */ function language_initialize($type) { // Execute the language negotiation providers in the order they were set up and return the // first valid language found. $negotiation = variable_get("language_negotiation_$type", array()); foreach ($negotiation as $provider_id => $provider) { $language = language_provider_invoke($provider_id, $provider); if ($language) { $language->provider = $provider_id; return $language; } } // If no other language was found use the default one. $language = language_default(); $language->provider = LANGUAGE_NEGOTIATION_DEFAULT; return $language; } /** * Returns the default language negotiation provider. * * @return * The default language code. */ function language_from_default() { return language_default()->language; } /** * Splits the given path into prefix and actual path. * * Parse the given path and return the language object identified by the prefix * and the actual path. * * @param $path * The path to split. * @param $languages * An array of valid languages. * * @return * An array composed of: * - A language object corresponding to the identified prefix on success, * FALSE otherwise. * - The path without the prefix on success, the given path otherwise. */ function language_url_split_prefix($path, $languages) { $args = empty($path) ? array() : explode('/', $path); $prefix = array_shift($args); // Search prefix within enabled languages. foreach ($languages as $language) { if (!empty($language->prefix) && $language->prefix == $prefix) { // Rebuild $path with the language removed. return array($language, implode('/', $args)); } } return array(FALSE, $path); } /** * Returns the possible fallback languages ordered by language weight. * * @param * (optional) The language type. Defaults to LANGUAGE_TYPE_CONTENT. * * @return * An array of language codes. */ function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) { $fallback_candidates = &drupal_static(__FUNCTION__); if (!isset($fallback_candidates)) { $fallback_candidates = array(); // Get languages ordered by weight. // Use array keys to avoid duplicated entries. foreach (language_list('weight') as $languages) { foreach ($languages as $language) { $fallback_candidates[$language->language] = NULL; } } $fallback_candidates = array_keys($fallback_candidates); $fallback_candidates[] = LANGUAGE_NONE; // Let other modules hook in and add/change candidates. drupal_alter('language_fallback_candidates', $fallback_candidates); } return $fallback_candidates; } /** * @} End of "language_negotiation" */ drupal-7.26/includes/file.inc0000644001412200141220000025542512265562324015455 0ustar benderbender $info) { // Add defaults. $wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL); } drupal_alter('stream_wrappers', $wrappers); $existing = stream_get_wrappers(); foreach ($wrappers as $scheme => $info) { // We only register classes that implement our interface. if (in_array('DrupalStreamWrapperInterface', class_implements($info['class']), TRUE)) { // Record whether we are overriding an existing scheme. if (in_array($scheme, $existing, TRUE)) { $wrappers[$scheme]['override'] = TRUE; stream_wrapper_unregister($scheme); } else { $wrappers[$scheme]['override'] = FALSE; } if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) { stream_wrapper_register($scheme, $info['class']); } else { stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL); } } // Pre-populate the static cache with the filters most typically used. $wrappers_storage[STREAM_WRAPPERS_ALL][$scheme] = $wrappers[$scheme]; if (($info['type'] & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) { $wrappers_storage[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[$scheme]; } } } if (!isset($wrappers_storage[$filter])) { $wrappers_storage[$filter] = array(); foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) { // Bit-wise filter. if (($info['type'] & $filter) == $filter) { $wrappers_storage[$filter][$scheme] = $info; } } } return $wrappers_storage[$filter]; } /** * Returns the stream wrapper class name for a given scheme. * * @param $scheme * Stream scheme. * * @return * Return string if a scheme has a registered handler, or FALSE. */ function file_stream_wrapper_get_class($scheme) { $wrappers = file_get_stream_wrappers(); return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class']; } /** * Returns the scheme of a URI (e.g. a stream). * * @param $uri * A stream, referenced as "scheme://target". * * @return * A string containing the name of the scheme, or FALSE if none. For example, * the URI "public://example.txt" would return "public". * * @see file_uri_target() */ function file_uri_scheme($uri) { $position = strpos($uri, '://'); return $position ? substr($uri, 0, $position) : FALSE; } /** * Checks that the scheme of a stream URI is valid. * * Confirms that there is a registered stream handler for the provided scheme * and that it is callable. This is useful if you want to confirm a valid * scheme without creating a new instance of the registered handler. * * @param $scheme * A URI scheme, a stream is referenced as "scheme://target". * * @return * Returns TRUE if the string is the name of a validated stream, * or FALSE if the scheme does not have a registered handler. */ function file_stream_wrapper_valid_scheme($scheme) { // Does the scheme have a registered handler that is callable? $class = file_stream_wrapper_get_class($scheme); if (class_exists($class)) { return TRUE; } else { return FALSE; } } /** * Returns the part of a URI after the schema. * * @param $uri * A stream, referenced as "scheme://target". * * @return * A string containing the target (path), or FALSE if none. * For example, the URI "public://sample/test.txt" would return * "sample/test.txt". * * @see file_uri_scheme() */ function file_uri_target($uri) { $data = explode('://', $uri, 2); // Remove erroneous leading or trailing, forward-slashes and backslashes. return count($data) == 2 ? trim($data[1], '\/') : FALSE; } /** * Gets the default file stream implementation. * * @return * 'public', 'private' or any other file scheme defined as the default. */ function file_default_scheme() { return variable_get('file_default_scheme', 'public'); } /** * Normalizes a URI by making it syntactically correct. * * A stream is referenced as "scheme://target". * * The following actions are taken: * - Remove trailing slashes from target * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://". * * @param $uri * String reference containing the URI to normalize. * * @return * The normalized URI. */ function file_stream_wrapper_uri_normalize($uri) { $scheme = file_uri_scheme($uri); if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { $target = file_uri_target($uri); if ($target !== FALSE) { $uri = $scheme . '://' . $target; } } return $uri; } /** * Returns a reference to the stream wrapper class responsible for a given URI. * * The scheme determines the stream wrapper class that should be * used by consulting the stream wrapper registry. * * @param $uri * A stream, referenced as "scheme://target". * * @return * Returns a new stream wrapper object appropriate for the given URI or FALSE * if no registered handler could be found. For example, a URI of * "private://example.txt" would return a new private stream wrapper object * (DrupalPrivateStreamWrapper). */ function file_stream_wrapper_get_instance_by_uri($uri) { $scheme = file_uri_scheme($uri); $class = file_stream_wrapper_get_class($scheme); if (class_exists($class)) { $instance = new $class(); $instance->setUri($uri); return $instance; } else { return FALSE; } } /** * Returns a reference to the stream wrapper class responsible for a scheme. * * This helper method returns a stream instance using a scheme. That is, the * passed string does not contain a "://". For example, "public" is a scheme * but "public://" is a URI (stream). This is because the later contains both * a scheme and target despite target being empty. * * Note: the instance URI will be initialized to "scheme://" so that you can * make the customary method calls as if you had retrieved an instance by URI. * * @param $scheme * If the stream was "public://target", "public" would be the scheme. * * @return * Returns a new stream wrapper object appropriate for the given $scheme. * For example, for the public scheme a stream wrapper object * (DrupalPublicStreamWrapper). * FALSE is returned if no registered handler could be found. */ function file_stream_wrapper_get_instance_by_scheme($scheme) { $class = file_stream_wrapper_get_class($scheme); if (class_exists($class)) { $instance = new $class(); $instance->setUri($scheme . '://'); return $instance; } else { return FALSE; } } /** * Creates a web-accessible URL for a stream to an external or local file. * * Compatibility: normal paths and stream wrappers. * * There are two kinds of local files: * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper. * These are files that have either been uploaded by users or were generated * automatically (for example through CSS aggregation). * - "shipped files", i.e. those outside of the files directory, which ship as * part of Drupal core or contributed modules or themes. * * @param $uri * The URI to a file for which we need an external URL, or the path to a * shipped file. * * @return * A string containing a URL that may be used to access the file. * If the provided string already contains a preceding 'http', 'https', or * '/', nothing is done and the same string is returned. If a stream wrapper * could not be found to generate an external URL, then FALSE is returned. * * @see http://drupal.org/node/515192 */ function file_create_url($uri) { // Allow the URI to be altered, e.g. to serve a file from a CDN or static // file server. drupal_alter('file_url', $uri); $scheme = file_uri_scheme($uri); if (!$scheme) { // Allow for: // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg) // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to // http://example.com/bar.jpg by the browser when viewing a page over // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page) // Both types of relative URIs are characterized by a leading slash, hence // we can use a single check. if (drupal_substr($uri, 0, 1) == '/') { return $uri; } else { // If this is not a properly formatted stream, then it is a shipped file. // Therefore, return the urlencoded URI with the base URL prepended. return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri); } } elseif ($scheme == 'http' || $scheme == 'https') { // Check for HTTP so that we don't have to implement getExternalUrl() for // the HTTP wrapper. return $uri; } else { // Attempt to return an external URL using the appropriate wrapper. if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { return $wrapper->getExternalUrl(); } else { return FALSE; } } } /** * Checks that the directory exists and is writable. * * Directories need to have execute permissions to be considered a directory by * FTP servers, etc. * * @param $directory * A string reference containing the name of a directory path or URI. A * trailing slash will be trimmed from a path. * @param $options * A bitmask to indicate if the directory should be created if it does * not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only * (FILE_MODIFY_PERMISSIONS). * * @return * TRUE if the directory exists (or was created) and is writable. FALSE * otherwise. */ function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) { if (!file_stream_wrapper_valid_scheme(file_uri_scheme($directory))) { // Only trim if we're not dealing with a stream. $directory = rtrim($directory, '/\\'); } // Check if directory exists. if (!is_dir($directory)) { // Let mkdir() recursively create directories and use the default directory // permissions. if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) { return drupal_chmod($directory); } return FALSE; } // The directory exists, so check to see if it is writable. $writable = is_writable($directory); if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) { return drupal_chmod($directory); } return $writable; } /** * Creates a .htaccess file in each Drupal files directory if it is missing. */ function file_ensure_htaccess() { file_create_htaccess('public://', FALSE); if (variable_get('file_private_path', FALSE)) { file_create_htaccess('private://', TRUE); } file_create_htaccess('temporary://', TRUE); } /** * Creates a .htaccess file in the given directory. * * @param $directory * The directory. * @param $private * FALSE indicates that $directory should be an open and public directory. * The default is TRUE which indicates a private and protected directory. * @param $force_overwrite * Set to TRUE to attempt to overwrite the existing .htaccess file if one is * already present. Defaults to FALSE. */ function file_create_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) { if (file_uri_scheme($directory)) { $directory = file_stream_wrapper_uri_normalize($directory); } else { $directory = rtrim($directory, '/\\'); } $htaccess_path = $directory . '/.htaccess'; if (file_exists($htaccess_path) && !$force_overwrite) { // Short circuit if the .htaccess file already exists. return; } $htaccess_lines = file_htaccess_lines($private); // Write the .htaccess file. if (file_put_contents($htaccess_path, $htaccess_lines)) { drupal_chmod($htaccess_path, 0444); } else { $variables = array('%directory' => $directory, '!htaccess' => '
      ' . nl2br(check_plain($htaccess_lines))); watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: !htaccess", $variables, WATCHDOG_ERROR); } } /** * Returns the standard .htaccess lines that Drupal writes to file directories. * * @param $private * (Optional) Set to FALSE to return the .htaccess lines for an open and * public directory. The default is TRUE, which returns the .htaccess lines * for a private and protected directory. * * @return * A string representing the desired contents of the .htaccess file. * * @see file_create_htaccess() */ function file_htaccess_lines($private = TRUE) { $lines = << # Override the handler again if we're run later in the evaluation list. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003 # If we know how to do it safely, disable the PHP engine entirely. php_flag engine off EOF; if ($private) { $lines = "Deny from all\n\n" . $lines; } return $lines; } /** * Loads file objects from the database. * * @param $fids * An array of file IDs. * @param $conditions * (deprecated) An associative array of conditions on the {file_managed} * table, where the keys are the database fields and the values are the * values those fields must have. Instead, it is preferable to use * EntityFieldQuery to retrieve a list of entity IDs loadable by * this function. * * @return * An array of file objects, indexed by fid. * * @todo Remove $conditions in Drupal 8. * * @see hook_file_load() * @see file_load() * @see entity_load() * @see EntityFieldQuery */ function file_load_multiple($fids = array(), $conditions = array()) { return entity_load('file', $fids, $conditions); } /** * Loads a single file object from the database. * * @param $fid * A file ID. * * @return * An object representing the file, or FALSE if the file was not found. * * @see hook_file_load() * @see file_load_multiple() */ function file_load($fid) { $files = file_load_multiple(array($fid), array()); return reset($files); } /** * Saves a file object to the database. * * If the $file->fid is not set a new record will be added. * * @param $file * A file object returned by file_load(). * * @return * The updated file object. * * @see hook_file_insert() * @see hook_file_update() */ function file_save(stdClass $file) { $file->timestamp = REQUEST_TIME; $file->filesize = filesize($file->uri); // Load the stored entity, if any. if (!empty($file->fid) && !isset($file->original)) { $file->original = entity_load_unchanged('file', $file->fid); } module_invoke_all('file_presave', $file); module_invoke_all('entity_presave', $file, 'file'); if (empty($file->fid)) { drupal_write_record('file_managed', $file); // Inform modules about the newly added file. module_invoke_all('file_insert', $file); module_invoke_all('entity_insert', $file, 'file'); } else { drupal_write_record('file_managed', $file, 'fid'); // Inform modules that the file has been updated. module_invoke_all('file_update', $file); module_invoke_all('entity_update', $file, 'file'); } unset($file->original); return $file; } /** * Determines where a file is used. * * @param $file * A file object. * * @return * A nested array with usage data. The first level is keyed by module name, * the second by object type and the third by the object id. The value * of the third level contains the usage count. * * @see file_usage_add() * @see file_usage_delete() */ function file_usage_list(stdClass $file) { $result = db_select('file_usage', 'f') ->fields('f', array('module', 'type', 'id', 'count')) ->condition('fid', $file->fid) ->condition('count', 0, '>') ->execute(); $references = array(); foreach ($result as $usage) { $references[$usage->module][$usage->type][$usage->id] = $usage->count; } return $references; } /** * Records that a module is using a file. * * This usage information will be queried during file_delete() to ensure that * a file is not in use before it is physically removed from disk. * * Examples: * - A module that associates files with nodes, so $type would be * 'node' and $id would be the node's nid. Files for all revisions are stored * within a single nid. * - The User module associates an image with a user, so $type would be 'user' * and the $id would be the user's uid. * * @param $file * A file object. * @param $module * The name of the module using the file. * @param $type * The type of the object that contains the referenced file. * @param $id * The unique, numeric ID of the object containing the referenced file. * @param $count * (optional) The number of references to add to the object. Defaults to 1. * * @see file_usage_list() * @see file_usage_delete() */ function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) { db_merge('file_usage') ->key(array( 'fid' => $file->fid, 'module' => $module, 'type' => $type, 'id' => $id, )) ->fields(array('count' => $count)) ->expression('count', 'count + :count', array(':count' => $count)) ->execute(); } /** * Removes a record to indicate that a module is no longer using a file. * * The file_delete() function is typically called after removing a file usage * to remove the record from the file_managed table and delete the file itself. * * @param $file * A file object. * @param $module * The name of the module using the file. * @param $type * (optional) The type of the object that contains the referenced file. May * be omitted if all module references to a file are being deleted. * @param $id * (optional) The unique, numeric ID of the object containing the referenced * file. May be omitted if all module references to a file are being deleted. * @param $count * (optional) The number of references to delete from the object. Defaults to * 1. 0 may be specified to delete all references to the file within a * specific object. * * @see file_usage_add() * @see file_usage_list() * @see file_delete() */ function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $count = 1) { // Delete rows that have a exact or less value to prevent empty rows. $query = db_delete('file_usage') ->condition('module', $module) ->condition('fid', $file->fid); if ($type && $id) { $query ->condition('type', $type) ->condition('id', $id); } if ($count) { $query->condition('count', $count, '<='); } $result = $query->execute(); // If the row has more than the specified count decrement it by that number. if (!$result && $count > 0) { $query = db_update('file_usage') ->condition('module', $module) ->condition('fid', $file->fid); if ($type && $id) { $query ->condition('type', $type) ->condition('id', $id); } $query->expression('count', 'count - :count', array(':count' => $count)); $query->execute(); } } /** * Copies a file to a new location and adds a file record to the database. * * This function should be used when manipulating files that have records * stored in the database. This is a powerful function that in many ways * performs like an advanced version of copy(). * - Checks if $source and $destination are valid and readable/writable. * - If file already exists in $destination either the call will error out, * replace the file or rename the file based on the $replace parameter. * - If the $source and $destination are equal, the behavior depends on the * $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME * will rename the file until the $destination is unique. * - Adds the new file to the files database. If the source file is a * temporary file, the resulting file will also be a temporary file. See * file_save_upload() for details on temporary files. * * @param $source * A file object. * @param $destination * A string containing the destination that $source should be copied to. * This must be a stream wrapper URI. * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with * the destination name exists then its database entry will be updated. If * no database entry is found then a new one will be created. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return * File object if the copy is successful, or FALSE in the event of an error. * * @see file_unmanaged_copy() * @see hook_file_copy() */ function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { if (!file_valid_uri($destination)) { if (($realpath = drupal_realpath($source->uri)) !== FALSE) { watchdog('file', 'File %file (%realpath) could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination)); } else { watchdog('file', 'File %file could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination)); } drupal_set_message(t('The specified file %file could not be copied, because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error'); return FALSE; } if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) { $file = clone $source; $file->fid = NULL; $file->uri = $uri; $file->filename = drupal_basename($uri); // If we are replacing an existing file re-use its database record. if ($replace == FILE_EXISTS_REPLACE) { $existing_files = file_load_multiple(array(), array('uri' => $uri)); if (count($existing_files)) { $existing = reset($existing_files); $file->fid = $existing->fid; $file->filename = $existing->filename; } } // If we are renaming around an existing file (rather than a directory), // use its basename for the filename. elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { $file->filename = drupal_basename($destination); } $file = file_save($file); // Inform modules that the file has been copied. module_invoke_all('file_copy', $file, $source); return $file; } return FALSE; } /** * Determines whether the URI has a valid scheme for file API operations. * * There must be a scheme and it must be a Drupal-provided scheme like * 'public', 'private', 'temporary', or an extension provided with * hook_stream_wrappers(). * * @param $uri * The URI to be tested. * * @return * TRUE if the URI is allowed. */ function file_valid_uri($uri) { // Assert that the URI has an allowed scheme. Barepaths are not allowed. $uri_scheme = file_uri_scheme($uri); if (empty($uri_scheme) || !file_stream_wrapper_valid_scheme($uri_scheme)) { return FALSE; } return TRUE; } /** * Copies a file to a new location without invoking the file API. * * This is a powerful function that in many ways performs like an advanced * version of copy(). * - Checks if $source and $destination are valid and readable/writable. * - If file already exists in $destination either the call will error out, * replace the file or rename the file based on the $replace parameter. * - If the $source and $destination are equal, the behavior depends on the * $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME * will rename the file until the $destination is unique. * - Provides a fallback using realpaths if the move fails using stream * wrappers. This can occur because PHP's copy() function does not properly * support streams if safe_mode or open_basedir are enabled. See * https://bugs.php.net/bug.php?id=60456 * * @param $source * A string specifying the filepath or URI of the source file. * @param $destination * A URI containing the destination that $source should be copied to. The * URI may be a bare filepath (without a scheme). If this value is omitted, * Drupal's default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return * The path to the new file, or FALSE in the event of an error. * * @see file_copy() */ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { $original_source = $source; $original_destination = $destination; // Assert that the source file actually exists. if (!file_exists($source)) { // @todo Replace drupal_set_message() calls with exceptions instead. drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error'); if (($realpath = drupal_realpath($original_source)) !== FALSE) { watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath)); } else { watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source)); } return FALSE; } // Build a destination URI if necessary. if (!isset($destination)) { $destination = file_build_uri(drupal_basename($source)); } // Prepare the destination directory. if (file_prepare_directory($destination)) { // The destination is already a directory, so append the source basename. $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source)); } else { // Perhaps $destination is a dir/file? $dirname = drupal_dirname($destination); if (!file_prepare_directory($dirname)) { // The destination is not valid. watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname)); drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error'); return FALSE; } } // Determine whether we can perform this operation based on overwrite rules. $destination = file_destination($destination, $replace); if ($destination === FALSE) { drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error'); watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%directory' => $destination)); return FALSE; } // Assert that the source and destination filenames are not the same. $real_source = drupal_realpath($source); $real_destination = drupal_realpath($destination); if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) { drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error'); watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source)); return FALSE; } // Make sure the .htaccess files are present. file_ensure_htaccess(); // Perform the copy operation. if (!@copy($source, $destination)) { // If the copy failed and realpaths exist, retry the operation using them // instead. if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) { watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR); return FALSE; } } // Set the permissions on the new file. drupal_chmod($destination); return $destination; } /** * Constructs a URI to Drupal's default files location given a relative path. */ function file_build_uri($path) { $uri = file_default_scheme() . '://' . $path; return file_stream_wrapper_uri_normalize($uri); } /** * Determines the destination path for a file. * * @param $destination * A string specifying the desired final URI or filepath. * @param $replace * Replace behavior when the destination file already exists. * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return * The destination filepath, or FALSE if the file already exists * and FILE_EXISTS_ERROR is specified. */ function file_destination($destination, $replace) { if (file_exists($destination)) { switch ($replace) { case FILE_EXISTS_REPLACE: // Do nothing here, we want to overwrite the existing file. break; case FILE_EXISTS_RENAME: $basename = drupal_basename($destination); $directory = drupal_dirname($destination); $destination = file_create_filename($basename, $directory); break; case FILE_EXISTS_ERROR: // Error reporting handled by calling function. return FALSE; } } return $destination; } /** * Moves a file to a new location and update the file's database entry. * * Moving a file is performed by copying the file to the new location and then * deleting the original. * - Checks if $source and $destination are valid and readable/writable. * - Performs a file move if $source is not equal to $destination. * - If file already exists in $destination either the call will error out, * replace the file or rename the file based on the $replace parameter. * - Adds the new file to the files database. * * @param $source * A file object. * @param $destination * A string containing the destination that $source should be moved to. * This must be a stream wrapper URI. * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with * the destination name exists then its database entry will be updated and * file_delete() called on the source file after hook_file_move is called. * If no database entry is found then the source files record will be * updated. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return * Resulting file object for success, or FALSE in the event of an error. * * @see file_unmanaged_move() * @see hook_file_move() */ function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { if (!file_valid_uri($destination)) { if (($realpath = drupal_realpath($source->uri)) !== FALSE) { watchdog('file', 'File %file (%realpath) could not be moved, because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination)); } else { watchdog('file', 'File %file could not be moved, because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination)); } drupal_set_message(t('The specified file %file could not be moved, because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error'); return FALSE; } if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) { $delete_source = FALSE; $file = clone $source; $file->uri = $uri; // If we are replacing an existing file re-use its database record. if ($replace == FILE_EXISTS_REPLACE) { $existing_files = file_load_multiple(array(), array('uri' => $uri)); if (count($existing_files)) { $existing = reset($existing_files); $delete_source = TRUE; $file->fid = $existing->fid; } } // If we are renaming around an existing file (rather than a directory), // use its basename for the filename. elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { $file->filename = drupal_basename($destination); } $file = file_save($file); // Inform modules that the file has been moved. module_invoke_all('file_move', $file, $source); if ($delete_source) { // Try a soft delete to remove original if it's not in use elsewhere. file_delete($source); } return $file; } return FALSE; } /** * Moves a file to a new location without database changes or hook invocation. * * @param $source * A string specifying the filepath or URI of the original file. * @param $destination * A string containing the destination that $source should be moved to. * This must be a stream wrapper URI. If this value is omitted, Drupal's * default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return * The URI of the moved file, or FALSE in the event of an error. * * @see file_move() */ function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { $filepath = file_unmanaged_copy($source, $destination, $replace); if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) { return FALSE; } return $filepath; } /** * Modifies a filename as needed for security purposes. * * Munging a file name prevents unknown file extensions from masking exploit * files. When web servers such as Apache decide how to process a URL request, * they use the file extension. If the extension is not recognized, Apache * skips that extension and uses the previous file extension. For example, if * the file being requested is exploit.php.pps, and Apache does not recognize * the '.pps' extension, it treats the file as PHP and executes it. To make * this file name safe for Apache and prevent it from executing as PHP, the * .php extension is "munged" into .php_, making the safe file name * exploit.php_.pps. * * Specifically, this function adds an underscore to all extensions that are * between 2 and 5 characters in length, internal to the file name, and not * included in $extensions. * * Function behavior is also controlled by the Drupal variable * 'allow_insecure_uploads'. If 'allow_insecure_uploads' evaluates to TRUE, no * alterations will be made, if it evaluates to FALSE, the filename is 'munged'. * * @param $filename * File name to modify. * @param $extensions * A space-separated list of extensions that should not be altered. * @param $alerts * If TRUE, drupal_set_message() will be called to display a message if the * file name was changed. * * @return * The potentially modified $filename. */ function file_munge_filename($filename, $extensions, $alerts = TRUE) { $original = $filename; // Allow potentially insecure uploads for very savvy users and admin if (!variable_get('allow_insecure_uploads', 0)) { // Remove any null bytes. See http://php.net/manual/security.filesystem.nullbytes.php $filename = str_replace(chr(0), '', $filename); $whitelist = array_unique(explode(' ', trim($extensions))); // Split the filename up by periods. The first part becomes the basename // the last part the final extension. $filename_parts = explode('.', $filename); $new_filename = array_shift($filename_parts); // Remove file basename. $final_extension = array_pop($filename_parts); // Remove final extension. // Loop through the middle parts of the name and add an underscore to the // end of each section that could be a file extension but isn't in the list // of allowed extensions. foreach ($filename_parts as $filename_part) { $new_filename .= '.' . $filename_part; if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) { $new_filename .= '_'; } } $filename = $new_filename . '.' . $final_extension; if ($alerts && $original != $filename) { drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename))); } } return $filename; } /** * Undoes the effect of file_munge_filename(). * * @param $filename * String with the filename to be unmunged. * * @return * An unmunged filename string. */ function file_unmunge_filename($filename) { return str_replace('_.', '.', $filename); } /** * Creates a full file path from a directory and filename. * * If a file with the specified name already exists, an alternative will be * used. * * @param $basename * String filename * @param $directory * String containing the directory or parent URI. * * @return * File path consisting of $directory and a unique filename based off * of $basename. */ function file_create_filename($basename, $directory) { // Strip control characters (ASCII value < 32). Though these are allowed in // some filesystems, not many applications handle them well. $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename); if (substr(PHP_OS, 0, 3) == 'WIN') { // These characters are not allowed in Windows filenames $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename); } // A URI or path may already have a trailing slash or look like "public://". if (substr($directory, -1) == '/') { $separator = ''; } else { $separator = '/'; } $destination = $directory . $separator . $basename; if (file_exists($destination)) { // Destination file already exists, generate an alternative. $pos = strrpos($basename, '.'); if ($pos !== FALSE) { $name = substr($basename, 0, $pos); $ext = substr($basename, $pos); } else { $name = $basename; $ext = ''; } $counter = 0; do { $destination = $directory . $separator . $name . '_' . $counter++ . $ext; } while (file_exists($destination)); } return $destination; } /** * Deletes a file and its database record. * * If the $force parameter is not TRUE, file_usage_list() will be called to * determine if the file is being used by any modules. If the file is being * used the delete will be canceled. * * @param $file * A file object. * @param $force * Boolean indicating that the file should be deleted even if the file is * reported as in use by the file_usage table. * * @return mixed * TRUE for success, FALSE in the event of an error, or an array if the file * is being used by any modules. * * @see file_unmanaged_delete() * @see file_usage_list() * @see file_usage_delete() * @see hook_file_delete() */ function file_delete(stdClass $file, $force = FALSE) { if (!file_valid_uri($file->uri)) { if (($realpath = drupal_realpath($file->uri)) !== FALSE) { watchdog('file', 'File %file (%realpath) could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri, '%realpath' => $realpath)); } else { watchdog('file', 'File %file could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri)); } drupal_set_message(t('The specified file %file could not be deleted, because it is not a valid URI. More information is available in the system log.', array('%file' => $file->uri)), 'error'); return FALSE; } // If any module still has a usage entry in the file_usage table, the file // will not be deleted, but file_delete() will return a populated array // that tests as TRUE. if (!$force && ($references = file_usage_list($file))) { return $references; } // Let other modules clean up any references to the deleted file. module_invoke_all('file_delete', $file); module_invoke_all('entity_delete', $file, 'file'); // Make sure the file is deleted before removing its row from the // database, so UIs can still find the file in the database. if (file_unmanaged_delete($file->uri)) { db_delete('file_managed')->condition('fid', $file->fid)->execute(); db_delete('file_usage')->condition('fid', $file->fid)->execute(); return TRUE; } return FALSE; } /** * Deletes a file without database changes or hook invocations. * * This function should be used when the file to be deleted does not have an * entry recorded in the files table. * * @param $path * A string containing a file path or (streamwrapper) URI. * * @return * TRUE for success or path does not exist, or FALSE in the event of an * error. * * @see file_delete() * @see file_unmanaged_delete_recursive() */ function file_unmanaged_delete($path) { if (is_dir($path)) { watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR); return FALSE; } if (is_file($path)) { return drupal_unlink($path); } // Return TRUE for non-existent file, but log that nothing was actually // deleted, as the current state is the intended result. if (!file_exists($path)) { watchdog('file', 'The file %path was not deleted, because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE); return TRUE; } // We cannot handle anything other than files and directories. Log an error // for everything else (sockets, symbolic links, etc). watchdog('file', 'The file %path is not of a recognized type so it was not deleted.', array('%path' => $path), WATCHDOG_ERROR); return FALSE; } /** * Deletes all files and directories in the specified filepath recursively. * * If the specified path is a directory then the function will call itself * recursively to process the contents. Once the contents have been removed the * directory will also be removed. * * If the specified path is a file then it will be passed to * file_unmanaged_delete(). * * Note that this only deletes visible files with write permission. * * @param $path * A string containing either an URI or a file or directory path. * * @return * TRUE for success or if path does not exist, FALSE in the event of an * error. * * @see file_unmanaged_delete() */ function file_unmanaged_delete_recursive($path) { if (is_dir($path)) { $dir = dir($path); while (($entry = $dir->read()) !== FALSE) { if ($entry == '.' || $entry == '..') { continue; } $entry_path = $path . '/' . $entry; file_unmanaged_delete_recursive($entry_path); } $dir->close(); return drupal_rmdir($path); } return file_unmanaged_delete($path); } /** * Determines total disk space used by a single user or the whole filesystem. * * @param $uid * Optional. A user id, specifying NULL returns the total space used by all * non-temporary files. * @param $status * Optional. The file status to consider. The default is to only * consider files in status FILE_STATUS_PERMANENT. * * @return * An integer containing the number of bytes used. */ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { $query = db_select('file_managed', 'f'); $query->condition('f.status', $status); $query->addExpression('SUM(f.filesize)', 'filesize'); if (isset($uid)) { $query->condition('f.uid', $uid); } return $query->execute()->fetchField(); } /** * Saves a file upload to a new location. * * The file will be added to the {file_managed} table as a temporary file. * Temporary files are periodically cleaned. To make the file a permanent file, * assign the status and use file_save() to save the changes. * * @param $source * A string specifying the filepath or URI of the uploaded file to save. * @param $validators * An optional, associative array of callback functions used to validate the * file. See file_validate() for a full discussion of the array format. * If no extension validator is provided it will default to a limited safe * list of extensions which is as follows: "jpg jpeg gif png txt * doc xls pdf ppt pps odt ods odp". To allow all extensions you must * explicitly set the 'file_validate_extensions' validator to an empty array * (Beware: this is not safe and should only be allowed for trusted users, if * at all). * @param $destination * A string containing the URI $source should be copied to. * This must be a stream wrapper URI. If this value is omitted, Drupal's * temporary files scheme will be used ("temporary://"). * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE: Replace the existing file. * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR: Do nothing and return FALSE. * * @return * An object containing the file information if the upload succeeded, FALSE * in the event of an error, or NULL if no file was uploaded. The * documentation for the "File interface" group, which you can find under * Related topics, or the header at the top of this file, documents the * components of a file object. In addition to the standard components, * this function adds: * - source: Path to the file before it is moved. * - destination: Path to the file after it is moved (same as 'uri'). */ function file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) { global $user; static $upload_cache; // Return cached objects without processing since the file will have // already been processed and the paths in _FILES will be invalid. if (isset($upload_cache[$source])) { return $upload_cache[$source]; } // Make sure there's an upload to process. if (empty($_FILES['files']['name'][$source])) { return NULL; } // Check for file upload errors and return FALSE if a lower level system // error occurred. For a complete list of errors: // See http://php.net/manual/features.file-upload.errors.php. switch ($_FILES['files']['error'][$source]) { case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$source], '%maxsize' => format_size(file_upload_max_size()))), 'error'); return FALSE; case UPLOAD_ERR_PARTIAL: case UPLOAD_ERR_NO_FILE: drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $_FILES['files']['name'][$source])), 'error'); return FALSE; case UPLOAD_ERR_OK: // Final check that this is a valid upload, if it isn't, use the // default error handler. if (is_uploaded_file($_FILES['files']['tmp_name'][$source])) { break; } // Unknown error default: drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$source])), 'error'); return FALSE; } // Begin building file object. $file = new stdClass(); $file->uid = $user->uid; $file->status = 0; $file->filename = trim(drupal_basename($_FILES['files']['name'][$source]), '.'); $file->uri = $_FILES['files']['tmp_name'][$source]; $file->filemime = file_get_mimetype($file->filename); $file->filesize = $_FILES['files']['size'][$source]; $extensions = ''; if (isset($validators['file_validate_extensions'])) { if (isset($validators['file_validate_extensions'][0])) { // Build the list of non-munged extensions if the caller provided them. $extensions = $validators['file_validate_extensions'][0]; } else { // If 'file_validate_extensions' is set and the list is empty then the // caller wants to allow any extension. In this case we have to remove the // validator or else it will reject all extensions. unset($validators['file_validate_extensions']); } } else { // No validator was provided, so add one using the default list. // Build a default non-munged safe list for file_munge_filename(). $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; $validators['file_validate_extensions'] = array(); $validators['file_validate_extensions'][0] = $extensions; } if (!empty($extensions)) { // Munge the filename to protect against possible malicious extension hiding // within an unknown file type (ie: filename.html.foo). $file->filename = file_munge_filename($file->filename, $extensions); } // Rename potentially executable files, to help prevent exploits (i.e. will // rename filename.php.foo and filename.php to filename.php.foo.txt and // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' // evaluates to TRUE. if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { $file->filemime = 'text/plain'; $file->uri .= '.txt'; $file->filename .= '.txt'; // The .txt extension may not be in the allowed list of extensions. We have // to add it here or else the file upload will fail. if (!empty($extensions)) { $validators['file_validate_extensions'][0] .= ' txt'; drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename))); } } // If the destination is not provided, use the temporary directory. if (empty($destination)) { $destination = 'temporary://'; } // Assert that the destination contains a valid stream. $destination_scheme = file_uri_scheme($destination); if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) { drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error'); return FALSE; } $file->source = $source; // A URI may already have a trailing slash or look like "public://". if (substr($destination, -1) != '/') { $destination .= '/'; } $file->destination = file_destination($destination . $file->filename, $replace); // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and // there's an existing file so we need to bail. if ($file->destination === FALSE) { drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $source, '%directory' => $destination)), 'error'); return FALSE; } // Add in our check of the the file name length. $validators['file_validate_name_length'] = array(); // Call the validation functions specified by this function's caller. $errors = file_validate($file, $validators); // Check for errors. if (!empty($errors)) { $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename)); if (count($errors) > 1) { $message .= theme('item_list', array('items' => $errors)); } else { $message .= ' ' . array_pop($errors); } form_set_error($source, $message); return FALSE; } // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary // directory. This overcomes open_basedir restrictions for future file // operations. $file->uri = $file->destination; if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->uri)) { form_set_error($source, t('File upload error. Could not move uploaded file.')); watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri)); return FALSE; } // Set the permissions on the new file. drupal_chmod($file->uri); // If we are replacing an existing file re-use its database record. if ($replace == FILE_EXISTS_REPLACE) { $existing_files = file_load_multiple(array(), array('uri' => $file->uri)); if (count($existing_files)) { $existing = reset($existing_files); $file->fid = $existing->fid; } } // If we made it this far it's safe to record this file in the database. if ($file = file_save($file)) { // Add file to the cache. $upload_cache[$source] = $file; return $file; } return FALSE; } /** * Moves an uploaded file to a new location. * * PHP's move_uploaded_file() does not properly support streams if safe_mode * or open_basedir are enabled, so this function fills that gap. * * Compatibility: normal paths and stream wrappers. * * @param $filename * The filename of the uploaded file. * @param $uri * A string containing the destination URI of the file. * * @return * TRUE on success, or FALSE on failure. * * @see move_uploaded_file() * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_move_uploaded_file($filename, $uri) { $result = @move_uploaded_file($filename, $uri); // PHP's move_uploaded_file() does not properly support streams if safe_mode // or open_basedir are enabled so if the move failed, try finding a real path // and retry the move operation. if (!$result) { if ($realpath = drupal_realpath($uri)) { $result = move_uploaded_file($filename, $realpath); } else { $result = move_uploaded_file($filename, $uri); } } return $result; } /** * Checks that a file meets the criteria specified by the validators. * * After executing the validator callbacks specified hook_file_validate() will * also be called to allow other modules to report errors about the file. * * @param $file * A Drupal file object. * @param $validators * An optional, associative array of callback functions used to validate the * file. The keys are function names and the values arrays of callback * parameters which will be passed in after the file object. The * functions should return an array of error messages; an empty array * indicates that the file passed validation. The functions will be called in * the order specified. * * @return * An array containing validation error messages. * * @see hook_file_validate() */ function file_validate(stdClass &$file, $validators = array()) { // Call the validation functions specified by this function's caller. $errors = array(); foreach ($validators as $function => $args) { if (function_exists($function)) { array_unshift($args, $file); $errors = array_merge($errors, call_user_func_array($function, $args)); } } // Let other modules perform validation on the new file. return array_merge($errors, module_invoke_all('file_validate', $file)); } /** * Checks for files with names longer than we can store in the database. * * @param $file * A Drupal file object. * * @return * An array. If the file name is too long, it will contain an error message. */ function file_validate_name_length(stdClass $file) { $errors = array(); if (empty($file->filename)) { $errors[] = t("The file's name is empty. Please give a name to the file."); } if (strlen($file->filename) > 240) { $errors[] = t("The file's name exceeds the 240 characters limit. Please rename the file and try again."); } return $errors; } /** * Checks that the filename ends with an allowed extension. * * @param $file * A Drupal file object. * @param $extensions * A string with a space separated list of allowed extensions. * * @return * An array. If the file extension is not allowed, it will contain an error * message. * * @see hook_file_validate() */ function file_validate_extensions(stdClass $file, $extensions) { $errors = array(); $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i'; if (!preg_match($regex, $file->filename)) { $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)); } return $errors; } /** * Checks that the file's size is below certain limits. * * This check is not enforced for the user #1. * * @param $file * A Drupal file object. * @param $file_limit * An integer specifying the maximum file size in bytes. Zero indicates that * no limit should be enforced. * @param $user_limit * An integer specifying the maximum number of bytes the user is allowed. * Zero indicates that no limit should be enforced. * * @return * An array. If the file size exceeds limits, it will contain an error * message. * * @see hook_file_validate() */ function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) { global $user; $errors = array(); // Bypass validation for uid = 1. if ($user->uid != 1) { if ($file_limit && $file->filesize > $file_limit) { $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit))); } // Save a query by only calling file_space_used() when a limit is provided. if ($user_limit && (file_space_used($user->uid) + $file->filesize) > $user_limit) { $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit))); } } return $errors; } /** * Checks that the file is recognized by image_get_info() as an image. * * @param $file * A Drupal file object. * * @return * An array. If the file is not an image, it will contain an error message. * * @see hook_file_validate() */ function file_validate_is_image(stdClass $file) { $errors = array(); $info = image_get_info($file->uri); if (!$info || empty($info['extension'])) { $errors[] = t('Only JPEG, PNG and GIF images are allowed.'); } return $errors; } /** * Verifies that image dimensions are within the specified maximum and minimum. * * Non-image files will be ignored. If a image toolkit is available the image * will be scaled to fit within the desired maximum dimensions. * * @param $file * A Drupal file object. This function may resize the file affecting its * size. * @param $maximum_dimensions * An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If * an image toolkit is installed the image will be resized down to these * dimensions. A value of 0 indicates no restriction on size, so resizing * will be attempted. * @param $minimum_dimensions * An optional string in the form WIDTHxHEIGHT. This will check that the * image meets a minimum size. A value of 0 indicates no restriction. * * @return * An array. If the file is an image and did not meet the requirements, it * will contain an error message. * * @see hook_file_validate() */ function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0, $minimum_dimensions = 0) { $errors = array(); // Check first that the file is an image. if ($info = image_get_info($file->uri)) { if ($maximum_dimensions) { // Check that it is smaller than the given dimensions. list($width, $height) = explode('x', $maximum_dimensions); if ($info['width'] > $width || $info['height'] > $height) { // Try to resize the image to fit the dimensions. if ($image = image_load($file->uri)) { image_scale($image, $width, $height); image_save($image); $file->filesize = $image->info['file_size']; drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions))); } else { $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions)); } } } if ($minimum_dimensions) { // Check that it is larger than the given dimensions. list($width, $height) = explode('x', $minimum_dimensions); if ($info['width'] < $width || $info['height'] < $height) { $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions)); } } } return $errors; } /** * Saves a file to the specified destination and creates a database entry. * * @param $data * A string containing the contents of the file. * @param $destination * A string containing the destination URI. This must be a stream wrapper URI. * If no value is provided, a randomized name will be generated and the file * will be saved using Drupal's default files scheme, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with * the destination name exists then its database entry will be updated. If * no database entry is found then a new one will be created. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return * A file object, or FALSE on error. * * @see file_unmanaged_save_data() */ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { global $user; if (empty($destination)) { $destination = file_default_scheme() . '://'; } if (!file_valid_uri($destination)) { watchdog('file', 'The data could not be saved because the destination %destination is invalid. This may be caused by improper use of file_save_data() or a missing stream wrapper.', array('%destination' => $destination)); drupal_set_message(t('The data could not be saved, because the destination is invalid. More information is available in the system log.'), 'error'); return FALSE; } if ($uri = file_unmanaged_save_data($data, $destination, $replace)) { // Create a file object. $file = new stdClass(); $file->fid = NULL; $file->uri = $uri; $file->filename = drupal_basename($uri); $file->filemime = file_get_mimetype($file->uri); $file->uid = $user->uid; $file->status = FILE_STATUS_PERMANENT; // If we are replacing an existing file re-use its database record. if ($replace == FILE_EXISTS_REPLACE) { $existing_files = file_load_multiple(array(), array('uri' => $uri)); if (count($existing_files)) { $existing = reset($existing_files); $file->fid = $existing->fid; $file->filename = $existing->filename; } } // If we are renaming around an existing file (rather than a directory), // use its basename for the filename. elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { $file->filename = drupal_basename($destination); } return file_save($file); } return FALSE; } /** * Saves a string to the specified destination without invoking file API. * * This function is identical to file_save_data() except the file will not be * saved to the {file_managed} table and none of the file_* hooks will be * called. * * @param $data * A string containing the contents of the file. * @param $destination * A string containing the destination location. This must be a stream wrapper * URI. If no value is provided, a randomized name will be generated and the * file will be saved using Drupal's default files scheme, usually * "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return * A string with the path of the resulting file, or FALSE on error. * * @see file_save_data() */ function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { // Write the data to a temporary file. $temp_name = drupal_tempnam('temporary://', 'file'); if (file_put_contents($temp_name, $data) === FALSE) { drupal_set_message(t('The file could not be created.'), 'error'); return FALSE; } // Move the file to its final destination. return file_unmanaged_move($temp_name, $destination, $replace); } /** * Transfers a file to the client using HTTP. * * Pipes a file through Drupal to the client. * * @param $uri * String specifying the file URI to transfer. * @param $headers * An array of HTTP headers to send along with file. */ function file_transfer($uri, $headers) { if (ob_get_level()) { ob_end_clean(); } foreach ($headers as $name => $value) { drupal_add_http_header($name, $value); } drupal_send_headers(); $scheme = file_uri_scheme($uri); // Transfer file in 1024 byte chunks to save memory usage. if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) { while (!feof($fd)) { print fread($fd, 1024); } fclose($fd); } else { drupal_not_found(); } drupal_exit(); } /** * Menu handler for private file transfers. * * Call modules that implement hook_file_download() to find out if a file is * accessible and what headers it should be transferred with. If one or more * modules returned headers the download will start with the returned headers. * If a module returns -1 drupal_access_denied() will be returned. If the file * exists but no modules responded drupal_access_denied() will be returned. * If the file does not exist drupal_not_found() will be returned. * * @see system_menu() */ function file_download() { // Merge remainder of arguments from GET['q'], into relative file path. $args = func_get_args(); $scheme = array_shift($args); $target = implode('/', $args); $uri = $scheme . '://' . $target; if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) { // Let other modules provide headers and controls access to the file. // module_invoke_all() uses array_merge_recursive() which merges header // values into a new array. To avoid that and allow modules to override // headers instead, use array_merge() to merge the returned arrays. $headers = array(); foreach (module_implements('file_download') as $module) { $function = $module . '_file_download'; $result = $function($uri); if ($result == -1) { // Throw away the headers received so far. $headers = array(); break; } if (isset($result) && is_array($result)) { $headers = array_merge($headers, $result); } } if (count($headers)) { file_transfer($uri, $headers); } drupal_access_denied(); } else { drupal_not_found(); } drupal_exit(); } /** * Finds all files that match a given mask in a given directory. * * Directories and files beginning with a period are excluded; this * prevents hidden files and directories (such as SVN working directories) * from being scanned. * * @param $dir * The base directory or URI to scan, without trailing slash. * @param $mask * The preg_match() regular expression of the files to find. * @param $options * An associative array of additional options, with the following elements: * - 'nomask': The preg_match() regular expression of the files to ignore. * Defaults to '/(\.\.?|CVS)$/'. * - 'callback': The callback function to call for each match. There is no * default callback. * - 'recurse': When TRUE, the directory scan will recurse the entire tree * starting at the provided directory. Defaults to TRUE. * - 'key': The key to be used for the returned associative array of files. * Possible values are 'uri', for the file's URI; 'filename', for the * basename of the file; and 'name' for the name of the file without the * extension. Defaults to 'uri'. * - 'min_depth': Minimum depth of directories to return files from. Defaults * to 0. * @param $depth * Current depth of recursion. This parameter is only used internally and * should not be passed in. * * @return * An associative array (keyed on the chosen key) of objects with 'uri', * 'filename', and 'name' members corresponding to the matching files. */ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { // Merge in defaults. $options += array( 'nomask' => '/(\.\.?|CVS)$/', 'callback' => 0, 'recurse' => TRUE, 'key' => 'uri', 'min_depth' => 0, ); $options['key'] = in_array($options['key'], array('uri', 'filename', 'name')) ? $options['key'] : 'uri'; $files = array(); if (is_dir($dir) && $handle = opendir($dir)) { while (FALSE !== ($filename = readdir($handle))) { if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') { $uri = "$dir/$filename"; $uri = file_stream_wrapper_uri_normalize($uri); if (is_dir($uri) && $options['recurse']) { // Give priority to files in this folder by merging them in after any subdirectory files. $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files); } elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) { // Always use this match over anything already set in $files with the // same $$options['key']. $file = new stdClass(); $file->uri = $uri; $file->filename = $filename; $file->name = pathinfo($filename, PATHINFO_FILENAME); $key = $options['key']; $files[$file->$key] = $file; if ($options['callback']) { $options['callback']($uri); } } } } closedir($handle); } return $files; } /** * Determines the maximum file upload size by querying the PHP settings. * * @return * A file size limit in bytes based on the PHP upload_max_filesize and * post_max_size */ function file_upload_max_size() { static $max_size = -1; if ($max_size < 0) { // Start with post_max_size. $max_size = parse_size(ini_get('post_max_size')); // If upload_max_size is less, then reduce. Except if upload_max_size is // zero, which indicates no limit. $upload_max = parse_size(ini_get('upload_max_filesize')); if ($upload_max > 0 && $upload_max < $max_size) { $max_size = $upload_max; } } return $max_size; } /** * Determines an Internet Media Type or MIME type from a filename. * * @param $uri * A string containing the URI, path, or filename. * @param $mapping * An optional map of extensions to their mimetypes, in the form: * - 'mimetypes': a list of mimetypes, keyed by an identifier, * - 'extensions': the mapping itself, an associative array in which * the key is the extension (lowercase) and the value is the mimetype * identifier. If $mapping is NULL file_mimetype_mapping() is called. * * @return * The internet media type registered for the extension or * application/octet-stream for unknown extensions. * * @see file_default_mimetype_mapping() */ function file_get_mimetype($uri, $mapping = NULL) { if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { return $wrapper->getMimeType($uri, $mapping); } else { // getMimeType() is not implementation specific, so we can directly // call it without an instance. return DrupalLocalStreamWrapper::getMimeType($uri, $mapping); } } /** * Sets the permissions on a file or directory. * * This function will use the 'file_chmod_directory' and 'file_chmod_file' * variables for the default modes for directories and uploaded/generated * files. By default these will give everyone read access so that users * accessing the files with a user account without the webserver group (e.g. * via FTP) can read these files, and give group write permissions so webserver * group members (e.g. a vhost account) can alter files uploaded and owned by * the webserver. * * PHP's chmod does not support stream wrappers so we use our wrapper * implementation which interfaces with chmod() by default. Contrib wrappers * may override this behavior in their implementations as needed. * * @param $uri * A string containing a URI file, or directory path. * @param $mode * Integer value for the permissions. Consult PHP chmod() documentation for * more information. * * @return * TRUE for success, FALSE in the event of an error. * * @ingroup php_wrappers */ function drupal_chmod($uri, $mode = NULL) { if (!isset($mode)) { if (is_dir($uri)) { $mode = variable_get('file_chmod_directory', 0775); } else { $mode = variable_get('file_chmod_file', 0664); } } // If this URI is a stream, pass it off to the appropriate stream wrapper. // Otherwise, attempt PHP's chmod. This allows use of drupal_chmod even // for unmanaged files outside of the stream wrapper interface. if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { if ($wrapper->chmod($mode)) { return TRUE; } } else { if (@chmod($uri, $mode)) { return TRUE; } } watchdog('file', 'The file permissions could not be set on %uri.', array('%uri' => $uri), WATCHDOG_ERROR); return FALSE; } /** * Deletes a file. * * PHP's unlink() is broken on Windows, as it can fail to remove a file * when it has a read-only flag set. * * @param $uri * A URI or pathname. * @param $context * Refer to http://php.net/manual/ref.stream.php * * @return * Boolean TRUE on success, or FALSE on failure. * * @see unlink() * @ingroup php_wrappers */ function drupal_unlink($uri, $context = NULL) { $scheme = file_uri_scheme($uri); if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme)) && (substr(PHP_OS, 0, 3) == 'WIN')) { chmod($uri, 0600); } if ($context) { return unlink($uri, $context); } else { return unlink($uri); } } /** * Resolves the absolute filepath of a local URI or filepath. * * The use of drupal_realpath() is discouraged, because it does not work for * remote URIs. Except in rare cases, URIs should not be manually resolved. * * Only use this function if you know that the stream wrapper in the URI uses * the local file system, and you need to pass an absolute path to a function * that is incompatible with stream URIs. * * @param string $uri * A stream wrapper URI or a filepath, possibly including one or more symbolic * links. * * @return string|false * The absolute local filepath (with no symbolic links), or FALSE on failure. * * @see DrupalStreamWrapperInterface::realpath() * @see http://php.net/manual/function.realpath.php * @ingroup php_wrappers */ function drupal_realpath($uri) { // If this URI is a stream, pass it off to the appropriate stream wrapper. // Otherwise, attempt PHP's realpath. This allows use of drupal_realpath even // for unmanaged files outside of the stream wrapper interface. if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { return $wrapper->realpath(); } // Check that the URI has a value. There is a bug in PHP 5.2 on *BSD systems // that makes realpath not return FALSE as expected when passing an empty // variable. // @todo Remove when Drupal drops support for PHP 5.2. elseif (!empty($uri)) { return realpath($uri); } return FALSE; } /** * Gets the name of the directory from a given path. * * PHP's dirname() does not properly pass streams, so this function fills * that gap. It is backwards compatible with normal paths and will use * PHP's dirname() as a fallback. * * Compatibility: normal paths and stream wrappers. * * @param $uri * A URI or path. * * @return * A string containing the directory name. * * @see dirname() * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_dirname($uri) { $scheme = file_uri_scheme($uri); if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { return file_stream_wrapper_get_instance_by_scheme($scheme)->dirname($uri); } else { return dirname($uri); } } /** * Gets the filename from a given path. * * PHP's basename() does not properly support streams or filenames beginning * with a non-US-ASCII character. * * @see http://bugs.php.net/bug.php?id=37738 * @see basename() * * @ingroup php_wrappers */ function drupal_basename($uri, $suffix = NULL) { $separators = '/'; if (DIRECTORY_SEPARATOR != '/') { // For Windows OS add special separator. $separators .= DIRECTORY_SEPARATOR; } // Remove right-most slashes when $uri points to directory. $uri = rtrim($uri, $separators); // Returns the trailing part of the $uri starting after one of the directory // separators. $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : ''; // Cuts off a suffix from the filename. if ($suffix) { $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename); } return $filename; } /** * Creates a directory using Drupal's default mode. * * PHP's mkdir() does not respect Drupal's default permissions mode. If a mode * is not provided, this function will make sure that Drupal's is used. * * Compatibility: normal paths and stream wrappers. * * @param $uri * A URI or pathname. * @param $mode * By default the Drupal mode is used. * @param $recursive * Default to FALSE. * @param $context * Refer to http://php.net/manual/ref.stream.php * * @return * Boolean TRUE on success, or FALSE on failure. * * @see mkdir() * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { if (!isset($mode)) { $mode = variable_get('file_chmod_directory', 0775); } if (!isset($context)) { return mkdir($uri, $mode, $recursive); } else { return mkdir($uri, $mode, $recursive, $context); } } /** * Removes a directory. * * PHP's rmdir() is broken on Windows, as it can fail to remove a directory * when it has a read-only flag set. * * @param $uri * A URI or pathname. * @param $context * Refer to http://php.net/manual/ref.stream.php * * @return * Boolean TRUE on success, or FALSE on failure. * * @see rmdir() * @ingroup php_wrappers */ function drupal_rmdir($uri, $context = NULL) { $scheme = file_uri_scheme($uri); if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme)) && (substr(PHP_OS, 0, 3) == 'WIN')) { chmod($uri, 0700); } if ($context) { return rmdir($uri, $context); } else { return rmdir($uri); } } /** * Creates a file with a unique filename in the specified directory. * * PHP's tempnam() does not return a URI like we want. This function * will return a URI if given a URI, or it will return a filepath if * given a filepath. * * Compatibility: normal paths and stream wrappers. * * @param $directory * The directory where the temporary filename will be created. * @param $prefix * The prefix of the generated temporary filename. * Note: Windows uses only the first three characters of prefix. * * @return * The new temporary filename, or FALSE on failure. * * @see tempnam() * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_tempnam($directory, $prefix) { $scheme = file_uri_scheme($directory); if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) { return $scheme . '://' . drupal_basename($filename); } else { return FALSE; } } else { // Handle as a normal tempnam() call. return tempnam($directory, $prefix); } } /** * Gets the path of system-appropriate temporary directory. */ function file_directory_temp() { $temporary_directory = variable_get('file_temporary_path', NULL); if (empty($temporary_directory)) { $directories = array(); // Has PHP been set with an upload_tmp_dir? if (ini_get('upload_tmp_dir')) { $directories[] = ini_get('upload_tmp_dir'); } // Operating system specific dirs. if (substr(PHP_OS, 0, 3) == 'WIN') { $directories[] = 'c:\\windows\\temp'; $directories[] = 'c:\\winnt\\temp'; } else { $directories[] = '/tmp'; } // PHP may be able to find an alternative tmp directory. // This function exists in PHP 5 >= 5.2.1, but Drupal // requires PHP 5 >= 5.2.0, so we check for it. if (function_exists('sys_get_temp_dir')) { $directories[] = sys_get_temp_dir(); } foreach ($directories as $directory) { if (is_dir($directory) && is_writable($directory)) { $temporary_directory = $directory; break; } } if (empty($temporary_directory)) { // If no directory has been found default to 'files/tmp'. $temporary_directory = variable_get('file_public_path', conf_path() . '/files') . '/tmp'; // Windows accepts paths with either slash (/) or backslash (\), but will // not accept a path which contains both a slash and a backslash. Since // the 'file_public_path' variable may have either format, we sanitize // everything to use slash which is supported on all platforms. $temporary_directory = str_replace('\\', '/', $temporary_directory); } // Save the path of the discovered directory. variable_set('file_temporary_path', $temporary_directory); } return $temporary_directory; } /** * Examines a file object and returns appropriate content headers for download. * * @param $file * A file object. * * @return * An associative array of headers, as expected by file_transfer(). */ function file_get_content_headers($file) { $name = mime_header_encode($file->filename); $type = mime_header_encode($file->filemime); return array( 'Content-Type' => $type, 'Content-Length' => $file->filesize, 'Cache-Control' => 'private', ); } /** * @} End of "defgroup file". */ drupal-7.26/includes/cache.inc0000644001412200141220000004703612265562324015576 0ustar benderbenderget($cid); } /** * Returns data from the persistent cache when given an array of cache IDs. * * @param $cids * An array of cache IDs for the data to retrieve. This is passed by * reference, and will have the IDs successfully returned from cache removed. * @param $bin * The cache bin where the data is stored. * * @return * An array of the items successfully returned from cache indexed by cid. */ function cache_get_multiple(array &$cids, $bin = 'cache') { return _cache_get_object($bin)->getMultiple($cids); } /** * Stores data in the persistent cache. * * The persistent cache is split up into several cache bins. In the default * cache implementation, each cache bin corresponds to a database table by the * same name. Other implementations might want to store several bins in data * structures that get flushed together. While it is not a problem for most * cache bins if the entries in them are flushed before their expire time, some * might break functionality or are extremely expensive to recalculate. The * other bins are expired automatically by core. Contributed modules can add * additional bins and get them expired automatically by implementing * hook_flush_caches(). * * The reasons for having several bins are as follows: * - Smaller bins mean smaller database tables and allow for faster selects and * inserts. * - We try to put fast changing cache items and rather static ones into * different bins. The effect is that only the fast changing bins will need a * lot of writes to disk. The more static bins will also be better cacheable * with MySQL's query cache. * * @param $cid * The cache ID of the data to store. * @param $data * The data to store in the cache. Complex data types will be automatically * serialized before insertion. Strings will be stored as plain text and are * not serialized. * @param $bin * The cache bin to store the data in. Valid core values are: * - cache: (default) Generic cache storage bin (used for theme registry, * locale date, list of simpletest tests, etc.). * - cache_block: Stores the content of various blocks. * - cache_bootstrap: Stores the class registry, the system list of modules, * the list of which modules implement which hooks, and the Drupal variable * list. * - cache_field: Stores the field data belonging to a given object. * - cache_filter: Stores filtered pieces of content. * - cache_form: Stores multistep forms. Flushing this bin means that some * forms displayed to users lose their state and the data already submitted * to them. This bin should not be flushed before its expired time. * - cache_menu: Stores the structure of visible navigation menus per page. * - cache_page: Stores generated pages for anonymous users. It is flushed * very often, whenever a page changes, at least for every node and comment * submission. This is the only bin affected by the page cache setting on * the administrator panel. * - cache_path: Stores the system paths that have an alias. * @param $expire * One of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. * * @see _update_cache_set() * @see cache_get() */ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { return _cache_get_object($bin)->set($cid, $data, $expire); } /** * Expires data from the cache. * * If called with the arguments $cid and $bin set to NULL or omitted, then * expirable entries will be cleared from the cache_page and cache_block bins, * and the $wildcard argument is ignored. * * @param $cid * If set, the cache ID or an array of cache IDs. Otherwise, all cache entries * that can expire are deleted. The $wildcard argument will be ignored if set * to NULL. * @param $bin * If set, the cache bin to delete from. Mandatory argument if $cid is set. * @param $wildcard * If TRUE, the $cid argument must contain a string value and cache IDs * starting with $cid are deleted in addition to the exact cache ID specified * by $cid. If $wildcard is TRUE and $cid is '*', the entire cache is emptied. */ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) { if (!isset($cid) && !isset($bin)) { // Clear the block cache first, so stale data will // not end up in the page cache. if (module_exists('block')) { cache_clear_all(NULL, 'cache_block'); } cache_clear_all(NULL, 'cache_page'); return; } return _cache_get_object($bin)->clear($cid, $wildcard); } /** * Checks if a cache bin is empty. * * A cache bin is considered empty if it does not contain any valid data for any * cache ID. * * @param $bin * The cache bin to check. * * @return * TRUE if the cache bin specified is empty. */ function cache_is_empty($bin) { return _cache_get_object($bin)->isEmpty(); } /** * Defines an interface for cache implementations. * * All cache implementations have to implement this interface. * DrupalDatabaseCache provides the default implementation, which can be * consulted as an example. * * To make Drupal use your implementation for a certain cache bin, you have to * set a variable with the name of the cache bin as its key and the name of * your class as its value. For example, if your implementation of * DrupalCacheInterface was called MyCustomCache, the following line would make * Drupal use it for the 'cache_page' bin: * @code * variable_set('cache_class_cache_page', 'MyCustomCache'); * @endcode * * Additionally, you can register your cache implementation to be used by * default for all cache bins by setting the variable 'cache_default_class' to * the name of your implementation of the DrupalCacheInterface, e.g. * @code * variable_set('cache_default_class', 'MyCustomCache'); * @endcode * * To implement a completely custom cache bin, use the same variable format: * @code * variable_set('cache_class_custom_bin', 'MyCustomCache'); * @endcode * To access your custom cache bin, specify the name of the bin when storing * or retrieving cached data: * @code * cache_set($cid, $data, 'custom_bin', $expire); * cache_get($cid, 'custom_bin'); * @endcode * * @see _cache_get_object() * @see DrupalDatabaseCache */ interface DrupalCacheInterface { /** * Returns data from the persistent cache. * * Data may be stored as either plain text or as serialized data. cache_get() * will automatically return unserialized objects and arrays. * * @param $cid * The cache ID of the data to retrieve. * * @return * The cache or FALSE on failure. */ function get($cid); /** * Returns data from the persistent cache when given an array of cache IDs. * * @param $cids * An array of cache IDs for the data to retrieve. This is passed by * reference, and will have the IDs successfully returned from cache * removed. * * @return * An array of the items successfully returned from cache indexed by cid. */ function getMultiple(&$cids); /** * Stores data in the persistent cache. * * @param $cid * The cache ID of the data to store. * @param $data * The data to store in the cache. Complex data types will be automatically * serialized before insertion. * Strings will be stored as plain text and not serialized. * @param $expire * One of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - CACHE_TEMPORARY: Indicates that the item should be removed at the next * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. */ function set($cid, $data, $expire = CACHE_PERMANENT); /** * Expires data from the cache. * * If called without arguments, expirable entries will be cleared from the * cache_page and cache_block bins. * * @param $cid * If set, the cache ID or an array of cache IDs. Otherwise, all cache * entries that can expire are deleted. The $wildcard argument will be * ignored if set to NULL. * @param $wildcard * If TRUE, the $cid argument must contain a string value and cache IDs * starting with $cid are deleted in addition to the exact cache ID * specified by $cid. If $wildcard is TRUE and $cid is '*', the entire * cache is emptied. */ function clear($cid = NULL, $wildcard = FALSE); /** * Checks if a cache bin is empty. * * A cache bin is considered empty if it does not contain any valid data for * any cache ID. * * @return * TRUE if the cache bin specified is empty. */ function isEmpty(); } /** * Defines a default cache implementation. * * This is Drupal's default cache implementation. It uses the database to store * cached data. Each cache bin corresponds to a database table by the same name. */ class DrupalDatabaseCache implements DrupalCacheInterface { protected $bin; /** * Constructs a DrupalDatabaseCache object. * * @param $bin * The cache bin for which the object is created. */ function __construct($bin) { $this->bin = $bin; } /** * Implements DrupalCacheInterface::get(). */ function get($cid) { $cids = array($cid); $cache = $this->getMultiple($cids); return reset($cache); } /** * Implements DrupalCacheInterface::getMultiple(). */ function getMultiple(&$cids) { try { // Garbage collection necessary when enforcing a minimum cache lifetime. $this->garbageCollection($this->bin); // When serving cached pages, the overhead of using db_select() was found // to add around 30% overhead to the request. Since $this->bin is a // variable, this means the call to db_query() here uses a concatenated // string. This is highly discouraged under any other circumstances, and // is used here only due to the performance overhead we would incur // otherwise. When serving an uncached page, the overhead of using // db_select() is a much smaller proportion of the request. $result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids)); $cache = array(); foreach ($result as $item) { $item = $this->prepareItem($item); if ($item) { $cache[$item->cid] = $item; } } $cids = array_diff($cids, array_keys($cache)); return $cache; } catch (Exception $e) { // If the database is never going to be available, cache requests should // return FALSE in order to allow exception handling to occur. return array(); } } /** * Garbage collection for get() and getMultiple(). * * @param $bin * The bin being requested. */ protected function garbageCollection() { $cache_lifetime = variable_get('cache_lifetime', 0); // Clean-up the per-user cache expiration session data, so that the session // handler can properly clean-up the session data for anonymous users. if (isset($_SESSION['cache_expiration'])) { $expire = REQUEST_TIME - $cache_lifetime; foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) { if ($timestamp < $expire) { unset($_SESSION['cache_expiration'][$bin]); } } if (!$_SESSION['cache_expiration']) { unset($_SESSION['cache_expiration']); } } // Garbage collection of temporary items is only necessary when enforcing // a minimum cache lifetime. if (!$cache_lifetime) { return; } // When cache lifetime is in force, avoid running garbage collection too // often since this will remove temporary cache items indiscriminately. $cache_flush = variable_get('cache_flush_' . $this->bin, 0); if ($cache_flush && ($cache_flush + $cache_lifetime <= REQUEST_TIME)) { // Reset the variable immediately to prevent a meltdown in heavy load situations. variable_set('cache_flush_' . $this->bin, 0); // Time to flush old cache data db_delete($this->bin) ->condition('expire', CACHE_PERMANENT, '<>') ->condition('expire', $cache_flush, '<=') ->execute(); } } /** * Prepares a cached item. * * Checks that items are either permanent or did not expire, and unserializes * data as appropriate. * * @param $cache * An item loaded from cache_get() or cache_get_multiple(). * * @return * The item with data unserialized as appropriate or FALSE if there is no * valid item to load. */ protected function prepareItem($cache) { global $user; if (!isset($cache->data)) { return FALSE; } // If the cached data is temporary and subject to a per-user minimum // lifetime, compare the cache entry timestamp with the user session // cache_expiration timestamp. If the cache entry is too old, ignore it. if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && isset($_SESSION['cache_expiration'][$this->bin]) && $_SESSION['cache_expiration'][$this->bin] > $cache->created) { // Ignore cache data that is too old and thus not valid for this user. return FALSE; } // If the data is permanent or not subject to a minimum cache lifetime, // unserialize and return the cached data. if ($cache->serialized) { $cache->data = unserialize($cache->data); } return $cache; } /** * Implements DrupalCacheInterface::set(). */ function set($cid, $data, $expire = CACHE_PERMANENT) { $fields = array( 'serialized' => 0, 'created' => REQUEST_TIME, 'expire' => $expire, ); if (!is_string($data)) { $fields['data'] = serialize($data); $fields['serialized'] = 1; } else { $fields['data'] = $data; $fields['serialized'] = 0; } try { db_merge($this->bin) ->key(array('cid' => $cid)) ->fields($fields) ->execute(); } catch (Exception $e) { // The database may not be available, so we'll ignore cache_set requests. } } /** * Implements DrupalCacheInterface::clear(). */ function clear($cid = NULL, $wildcard = FALSE) { global $user; if (empty($cid)) { if (variable_get('cache_lifetime', 0)) { // We store the time in the current user's session. We then simulate // that the cache was flushed for this user by not returning cached // data that was cached before the timestamp. $_SESSION['cache_expiration'][$this->bin] = REQUEST_TIME; $cache_flush = variable_get('cache_flush_' . $this->bin, 0); if ($cache_flush == 0) { // This is the first request to clear the cache, start a timer. variable_set('cache_flush_' . $this->bin, REQUEST_TIME); } elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) { // Clear the cache for everyone, cache_lifetime seconds have // passed since the first request to clear the cache. db_delete($this->bin) ->condition('expire', CACHE_PERMANENT, '<>') ->condition('expire', REQUEST_TIME, '<') ->execute(); variable_set('cache_flush_' . $this->bin, 0); } } else { // No minimum cache lifetime, flush all temporary cache entries now. db_delete($this->bin) ->condition('expire', CACHE_PERMANENT, '<>') ->condition('expire', REQUEST_TIME, '<') ->execute(); } } else { if ($wildcard) { if ($cid == '*') { // Check if $this->bin is a cache table before truncating. Other // cache_clear_all() operations throw a PDO error in this situation, // so we don't need to verify them first. This ensures that non-cache // tables cannot be truncated accidentally. if ($this->isValidBin()) { db_truncate($this->bin)->execute(); } else { throw new Exception(t('Invalid or missing cache bin specified: %bin', array('%bin' => $this->bin))); } } else { db_delete($this->bin) ->condition('cid', db_like($cid) . '%', 'LIKE') ->execute(); } } elseif (is_array($cid)) { // Delete in chunks when a large array is passed. do { db_delete($this->bin) ->condition('cid', array_splice($cid, 0, 1000), 'IN') ->execute(); } while (count($cid)); } else { db_delete($this->bin) ->condition('cid', $cid) ->execute(); } } } /** * Implements DrupalCacheInterface::isEmpty(). */ function isEmpty() { $this->garbageCollection(); $query = db_select($this->bin); $query->addExpression('1'); $result = $query->range(0, 1) ->execute() ->fetchField(); return empty($result); } /** * Checks if $this->bin represents a valid cache table. * * This check is required to ensure that non-cache tables are not truncated * accidentally when calling cache_clear_all(). * * @return boolean */ function isValidBin() { if ($this->bin == 'cache' || substr($this->bin, 0, 6) == 'cache_') { // Skip schema check for bins with standard table names. return TRUE; } // These fields are required for any cache table. $fields = array('cid', 'data', 'expire', 'created', 'serialized'); // Load the table schema. $schema = drupal_get_schema($this->bin); // Confirm that all fields are present. return isset($schema['fields']) && !array_diff($fields, array_keys($schema['fields'])); } } drupal-7.26/includes/filetransfer/0000755001412200141220000000000012265562324016512 5ustar benderbenderdrupal-7.26/includes/filetransfer/filetransfer.inc0000644001412200141220000002735112265562324021701 0ustar benderbenderjail = $jail; } /** * Classes that extend this class must override the factory() static method. * * @param string $jail * The full path where all file operations performed by this object will * be restricted to. This prevents the FileTransfer classes from being * able to touch other parts of the filesystem. * @param array $settings * An array of connection settings for the FileTransfer subclass. If the * getSettingsForm() method uses any nested settings, the same structure * will be assumed here. * @return object * New instance of the appropriate FileTransfer subclass. */ static function factory($jail, $settings) { throw new FileTransferException('FileTransfer::factory() static method not overridden by FileTransfer subclass.'); } /** * Implementation of the magic __get() method. * * If the connection isn't set to anything, this will call the connect() method * and set it to and return the result; afterwards, the connection will be * returned directly without using this method. */ function __get($name) { if ($name == 'connection') { $this->connect(); return $this->connection; } if ($name == 'chroot') { $this->setChroot(); return $this->chroot; } } /** * Connect to the server. */ abstract protected function connect(); /** * Copies a directory. * * @param $source * The source path. * @param $destination * The destination path. */ public final function copyDirectory($source, $destination) { $source = $this->sanitizePath($source); $destination = $this->fixRemotePath($destination); $this->checkPath($destination); $this->copyDirectoryJailed($source, $destination); } /** * @see http://php.net/chmod * * @param string $path * @param long $mode * @param bool $recursive */ public final function chmod($path, $mode, $recursive = FALSE) { if (!in_array('FileTransferChmodInterface', class_implements(get_class($this)))) { throw new FileTransferException('Unable to change file permissions'); } $path = $this->sanitizePath($path); $path = $this->fixRemotePath($path); $this->checkPath($path); $this->chmodJailed($path, $mode, $recursive); } /** * Creates a directory. * * @param $directory * The directory to be created. */ public final function createDirectory($directory) { $directory = $this->fixRemotePath($directory); $this->checkPath($directory); $this->createDirectoryJailed($directory); } /** * Removes a directory. * * @param $directory * The directory to be removed. */ public final function removeDirectory($directory) { $directory = $this->fixRemotePath($directory); $this->checkPath($directory); $this->removeDirectoryJailed($directory); } /** * Copies a file. * * @param $source * The source file. * @param $destination * The destination file. */ public final function copyFile($source, $destination) { $source = $this->sanitizePath($source); $destination = $this->fixRemotePath($destination); $this->checkPath($destination); $this->copyFileJailed($source, $destination); } /** * Removes a file. * * @param $destination * The destination file to be removed. */ public final function removeFile($destination) { $destination = $this->fixRemotePath($destination); $this->checkPath($destination); $this->removeFileJailed($destination); } /** * Checks that the path is inside the jail and throws an exception if not. * * @param $path * A path to check against the jail. */ protected final function checkPath($path) { $full_jail = $this->chroot . $this->jail; $full_path = drupal_realpath(substr($this->chroot . $path, 0, strlen($full_jail))); $full_path = $this->fixRemotePath($full_path, FALSE); if ($full_jail !== $full_path) { throw new FileTransferException('@directory is outside of the @jail', NULL, array('@directory' => $path, '@jail' => $this->jail)); } } /** * Returns a modified path suitable for passing to the server. * If a path is a windows path, makes it POSIX compliant by removing the drive letter. * If $this->chroot has a value, it is stripped from the path to allow for * chroot'd filetransfer systems. * * @param $path * @param $strip_chroot * * @return string */ protected final function fixRemotePath($path, $strip_chroot = TRUE) { $path = $this->sanitizePath($path); $path = preg_replace('|^([a-z]{1}):|i', '', $path); // Strip out windows driveletter if its there. if ($strip_chroot) { if ($this->chroot && strpos($path, $this->chroot) === 0) { $path = ($path == $this->chroot) ? '' : substr($path, strlen($this->chroot)); } } return $path; } /** * Changes backslashes to slashes, also removes a trailing slash. * * @param string $path * @return string */ function sanitizePath($path) { $path = str_replace('\\', '/', $path); // Windows path sanitization. if (substr($path, -1) == '/') { $path = substr($path, 0, -1); } return $path; } /** * Copies a directory. * * We need a separate method to make the $destination is in the jail. * * @param $source * The source path. * @param $destination * The destination path. */ protected function copyDirectoryJailed($source, $destination) { if ($this->isDirectory($destination)) { $destination = $destination . '/' . drupal_basename($source); } $this->createDirectory($destination); foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { $relative_path = substr($filename, strlen($source)); if ($file->isDir()) { $this->createDirectory($destination . $relative_path); } else { $this->copyFile($file->getPathName(), $destination . $relative_path); } } } /** * Creates a directory. * * @param $directory * The directory to be created. */ abstract protected function createDirectoryJailed($directory); /** * Removes a directory. * * @param $directory * The directory to be removed. */ abstract protected function removeDirectoryJailed($directory); /** * Copies a file. * * @param $source * The source file. * @param $destination * The destination file. */ abstract protected function copyFileJailed($source, $destination); /** * Removes a file. * * @param $destination * The destination file to be removed. */ abstract protected function removeFileJailed($destination); /** * Checks if a particular path is a directory * * @param $path * The path to check * * @return boolean */ abstract public function isDirectory($path); /** * Checks if a particular path is a file (not a directory). * * @param $path * The path to check * * @return boolean */ abstract public function isFile($path); /** * Return the chroot property for this connection. * * It does this by moving up the tree until it finds itself. If successful, * it will return the chroot, otherwise FALSE. * * @return * The chroot path for this connection or FALSE. */ function findChroot() { // If the file exists as is, there is no chroot. $path = __FILE__; $path = $this->fixRemotePath($path, FALSE); if ($this->isFile($path)) { return FALSE; } $path = dirname(__FILE__); $path = $this->fixRemotePath($path, FALSE); $parts = explode('/', $path); $chroot = ''; while (count($parts)) { $check = implode($parts, '/'); if ($this->isFile($check . '/' . drupal_basename(__FILE__))) { // Remove the trailing slash. return substr($chroot, 0, -1); } $chroot .= array_shift($parts) . '/'; } return FALSE; } /** * Sets the chroot and changes the jail to match the correct path scheme * */ function setChroot() { $this->chroot = $this->findChroot(); $this->jail = $this->fixRemotePath($this->jail); } /** * Returns a form to collect connection settings credentials. * * Implementing classes can either extend this form with fields collecting the * specific information they need, or override it entirely. */ public function getSettingsForm() { $form['username'] = array( '#type' => 'textfield', '#title' => t('Username'), ); $form['password'] = array( '#type' => 'password', '#title' => t('Password'), '#description' => t('Your password is not saved in the database and is only used to establish a connection.'), ); $form['advanced'] = array( '#type' => 'fieldset', '#title' => t('Advanced settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['advanced']['hostname'] = array( '#type' => 'textfield', '#title' => t('Host'), '#default_value' => 'localhost', '#description' => t('The connection will be created between your web server and the machine hosting the web server files. In the vast majority of cases, this will be the same machine, and "localhost" is correct.'), ); $form['advanced']['port'] = array( '#type' => 'textfield', '#title' => t('Port'), '#default_value' => NULL, ); return $form; } } /** * FileTransferException class. */ class FileTransferException extends Exception { public $arguments; function __construct($message, $code = 0, $arguments = array()) { parent::__construct($message, $code); $this->arguments = $arguments; } } /** * A FileTransfer Class implementing this interface can be used to chmod files. */ interface FileTransferChmodInterface { /** * Changes the permissions of the file / directory specified in $path * * @param string $path * Path to change permissions of. * @param long $mode * The new file permission mode to be passed to chmod(). * @param boolean $recursive * Pass TRUE to recursively chmod the entire directory specified in $path. */ function chmodJailed($path, $mode, $recursive); } /** * Provides an interface for iterating recursively over filesystem directories. * * Manually skips '.' and '..' directories, since no existing method is * available in PHP 5.2. * * @todo Depreciate in favor of RecursiveDirectoryIterator::SKIP_DOTS once PHP * 5.3 or later is required. */ class SkipDotsRecursiveDirectoryIterator extends RecursiveDirectoryIterator { /** * Constructs a SkipDotsRecursiveDirectoryIterator * * @param $path * The path of the directory to be iterated over. */ function __construct($path) { parent::__construct($path); $this->skipdots(); } function rewind() { parent::rewind(); $this->skipdots(); } function next() { parent::next(); $this->skipdots(); } protected function skipdots() { while ($this->isDot()) { parent::next(); } } } drupal-7.26/includes/filetransfer/ssh.inc0000644001412200141220000001003112265562324017775 0ustar benderbenderusername = $username; $this->password = $password; $this->hostname = $hostname; $this->port = $port; parent::__construct($jail); } function connect() { $this->connection = @ssh2_connect($this->hostname, $this->port); if (!$this->connection) { throw new FileTransferException('SSH Connection failed to @host:@port', NULL, array('@host' => $this->hostname, '@port' => $this->port)); } if (!@ssh2_auth_password($this->connection, $this->username, $this->password)) { throw new FileTransferException('The supplied username/password combination was not accepted.'); } } static function factory($jail, $settings) { $username = empty($settings['username']) ? '' : $settings['username']; $password = empty($settings['password']) ? '' : $settings['password']; $hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname']; $port = empty($settings['advanced']['port']) ? 22 : $settings['advanced']['port']; return new FileTransferSSH($jail, $username, $password, $hostname, $port); } protected function copyFileJailed($source, $destination) { if (!@ssh2_scp_send($this->connection, $source, $destination)) { throw new FileTransferException('Cannot copy @source_file to @destination_file.', NULL, array('@source' => $source, '@destination' => $destination)); } } protected function copyDirectoryJailed($source, $destination) { if (@!ssh2_exec($this->connection, 'cp -Rp ' . escapeshellarg($source) . ' ' . escapeshellarg($destination))) { throw new FileTransferException('Cannot copy directory @directory.', NULL, array('@directory' => $source)); } } protected function createDirectoryJailed($directory) { if (@!ssh2_exec($this->connection, 'mkdir ' . escapeshellarg($directory))) { throw new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory)); } } protected function removeDirectoryJailed($directory) { if (@!ssh2_exec($this->connection, 'rm -Rf ' . escapeshellarg($directory))) { throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory)); } } protected function removeFileJailed($destination) { if (!@ssh2_exec($this->connection, 'rm ' . escapeshellarg($destination))) { throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $destination)); } } /** * WARNING: This is untested. It is not currently used, but should do the trick. */ public function isDirectory($path) { $directory = escapeshellarg($path); $cmd = "[ -d {$directory} ] && echo 'yes'"; if ($output = @ssh2_exec($this->connection, $cmd)) { if ($output == 'yes') { return TRUE; } return FALSE; } else { throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path)); } } public function isFile($path) { $file = escapeshellarg($path); $cmd = "[ -f {$file} ] && echo 'yes'"; if ($output = @ssh2_exec($this->connection, $cmd)) { if ($output == 'yes') { return TRUE; } return FALSE; } else { throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path)); } } function chmodJailed($path, $mode, $recursive) { $cmd = sprintf("chmod %s%o %s", $recursive ? '-R ' : '', $mode, escapeshellarg($path)); if (@!ssh2_exec($this->connection, $cmd)) { throw new FileTransferException('Cannot change permissions of @path.', NULL, array('@path' => $path)); } } /** * Returns the form to configure the FileTransfer class for SSH. */ public function getSettingsForm() { $form = parent::getSettingsForm(); $form['advanced']['port']['#default_value'] = 22; return $form; } } drupal-7.26/includes/filetransfer/ftp.inc0000644001412200141220000001126612265562324020004 0ustar benderbenderusername = $username; $this->password = $password; $this->hostname = $hostname; $this->port = $port; parent::__construct($jail); } /** * Return an object which can implement the FTP protocol. * * @param string $jail * @param array $settings * @return FileTransferFTP * The appropriate FileTransferFTP subclass based on the available * options. If the FTP PHP extension is available, use it. */ static function factory($jail, $settings) { $username = empty($settings['username']) ? '' : $settings['username']; $password = empty($settings['password']) ? '' : $settings['password']; $hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname']; $port = empty($settings['advanced']['port']) ? 21 : $settings['advanced']['port']; if (function_exists('ftp_connect')) { $class = 'FileTransferFTPExtension'; } else { throw new FileTransferException('No FTP backend available.'); } return new $class($jail, $username, $password, $hostname, $port); } /** * Returns the form to configure the FileTransfer class for FTP. */ public function getSettingsForm() { $form = parent::getSettingsForm(); $form['advanced']['port']['#default_value'] = 21; return $form; } } class FileTransferFTPExtension extends FileTransferFTP implements FileTransferChmodInterface { public function connect() { $this->connection = ftp_connect($this->hostname, $this->port); if (!$this->connection) { throw new FileTransferException("Cannot connect to FTP Server, check settings"); } if (!ftp_login($this->connection, $this->username, $this->password)) { throw new FileTransferException("Cannot log in to FTP server. Check username and password"); } } protected function copyFileJailed($source, $destination) { if (!@ftp_put($this->connection, $destination, $source, FTP_BINARY)) { throw new FileTransferException("Cannot move @source to @destination", NULL, array("@source" => $source, "@destination" => $destination)); } } protected function createDirectoryJailed($directory) { if (!ftp_mkdir($this->connection, $directory)) { throw new FileTransferException("Cannot create directory @directory", NULL, array("@directory" => $directory)); } } protected function removeDirectoryJailed($directory) { $pwd = ftp_pwd($this->connection); if (!ftp_chdir($this->connection, $directory)) { throw new FileTransferException("Unable to change to directory @directory", NULL, array('@directory' => $directory)); } $list = @ftp_nlist($this->connection, '.'); if (!$list) { $list = array(); } foreach ($list as $item) { if ($item == '.' || $item == '..') { continue; } if (@ftp_chdir($this->connection, $item)) { ftp_cdup($this->connection); $this->removeDirectory(ftp_pwd($this->connection) . '/' . $item); } else { $this->removeFile(ftp_pwd($this->connection) . '/' . $item); } } ftp_chdir($this->connection, $pwd); if (!ftp_rmdir($this->connection, $directory)) { throw new FileTransferException("Unable to remove to directory @directory", NULL, array('@directory' => $directory)); } } protected function removeFileJailed($destination) { if (!ftp_delete($this->connection, $destination)) { throw new FileTransferException("Unable to remove to file @file", NULL, array('@file' => $destination)); } } public function isDirectory($path) { $result = FALSE; $curr = ftp_pwd($this->connection); if (@ftp_chdir($this->connection, $path)) { $result = TRUE; } ftp_chdir($this->connection, $curr); return $result; } public function isFile($path) { return ftp_size($this->connection, $path) != -1; } function chmodJailed($path, $mode, $recursive) { if (!ftp_chmod($this->connection, $mode, $path)) { throw new FileTransferException("Unable to set permissions on %file", NULL, array('%file' => $path)); } if ($this->isDirectory($path) && $recursive) { $filelist = @ftp_nlist($this->connection, $path); if (!$filelist) { //empty directory - returns false return; } foreach ($filelist as $file) { $this->chmodJailed($file, $mode, $recursive); } } } } if (!function_exists('ftp_chmod')) { function ftp_chmod($ftp_stream, $mode, $filename) { return ftp_site($ftp_stream, sprintf('CHMOD %o %s', $mode, $filename)); } } drupal-7.26/includes/filetransfer/local.inc0000644001412200141220000000533112265562324020301 0ustar benderbender $source, '%destination' => $destination)); } } protected function createDirectoryJailed($directory) { if (!is_dir($directory) && @!mkdir($directory, 0777, TRUE)) { throw new FileTransferException('Cannot create directory %directory.', NULL, array('%directory' => $directory)); } } protected function removeDirectoryJailed($directory) { if (!is_dir($directory)) { // Programmer error assertion, not something we expect users to see. throw new FileTransferException('removeDirectoryJailed() called with a path (%directory) that is not a directory.', NULL, array('%directory' => $directory)); } foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) { if ($file->isDir()) { if (@!drupal_rmdir($filename)) { throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $filename)); } } elseif ($file->isFile()) { if (@!drupal_unlink($filename)) { throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $filename)); } } } if (@!drupal_rmdir($directory)) { throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $directory)); } } protected function removeFileJailed($file) { if (@!drupal_unlink($file)) { throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $file)); } } public function isDirectory($path) { return is_dir($path); } public function isFile($path) { return is_file($path); } public function chmodJailed($path, $mode, $recursive) { if ($recursive && is_dir($path)) { foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { if (@!chmod($filename, $mode)) { throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $filename)); } } } elseif (@!chmod($path, $mode)) { throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $path)); } } } drupal-7.26/includes/install.inc0000644001412200141220000012607712265562324016204 0ustar benderbender $schema_version) { if ($schema_version > -1) { module_load_install($module); } } } /** * Returns an array of available schema versions for a module. * * @param $module * A module name. * @return * If the module has updates, an array of available updates sorted by version. * Otherwise, FALSE. */ function drupal_get_schema_versions($module) { $updates = &drupal_static(__FUNCTION__, NULL); if (!isset($updates[$module])) { $updates = array(); foreach (module_list() as $loaded_module) { $updates[$loaded_module] = array(); } // Prepare regular expression to match all possible defined hook_update_N(). $regexp = '/^(?P.+)_update_(?P\d+)$/'; $functions = get_defined_functions(); // Narrow this down to functions ending with an integer, since all // hook_update_N() functions end this way, and there are other // possible functions which match '_update_'. We use preg_grep() here // instead of foreaching through all defined functions, since the loop // through all PHP functions can take significant page execution time // and this function is called on every administrative page via // system_requirements(). foreach (preg_grep('/_\d+$/', $functions['user']) as $function) { // If this function is a module update function, add it to the list of // module updates. if (preg_match($regexp, $function, $matches)) { $updates[$matches['module']][] = $matches['version']; } } // Ensure that updates are applied in numerical order. foreach ($updates as &$module_updates) { sort($module_updates, SORT_NUMERIC); } } return empty($updates[$module]) ? FALSE : $updates[$module]; } /** * Returns the currently installed schema version for a module. * * @param $module * A module name. * @param $reset * Set to TRUE after modifying the system table. * @param $array * Set to TRUE if you want to get information about all modules in the * system. * @return * The currently installed schema version, or SCHEMA_UNINSTALLED if the * module is not installed. */ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) { static $versions = array(); if ($reset) { $versions = array(); } if (!$versions) { $versions = array(); $result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module')); foreach ($result as $row) { $versions[$row->name] = $row->schema_version; } } if ($array) { return $versions; } else { return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED; } } /** * Update the installed version information for a module. * * @param $module * A module name. * @param $version * The new schema version. */ function drupal_set_installed_schema_version($module, $version) { db_update('system') ->fields(array('schema_version' => $version)) ->condition('name', $module) ->execute(); // Reset the static cache of module schema versions. drupal_get_installed_schema_version(NULL, TRUE); } /** * Loads the installation profile, extracting its defined distribution name. * * @return * The distribution name defined in the profile's .info file. Defaults to * "Drupal" if none is explicitly provided by the installation profile. * * @see install_profile_info() */ function drupal_install_profile_distribution_name() { // During installation, the profile information is stored in the global // installation state (it might not be saved anywhere yet). if (drupal_installation_attempted()) { global $install_state; return $install_state['profile_info']['distribution_name']; } // At all other times, we load the profile via standard methods. else { $profile = drupal_get_profile(); $info = system_get_info('module', $profile); return $info['distribution_name']; } } /** * Detects the base URL using the PHP $_SERVER variables. * * @param $file * The name of the file calling this function so we can strip it out of * the URI when generating the base_url. * * @return * The auto-detected $base_url that should be configured in settings.php */ function drupal_detect_baseurl($file = 'install.php') { $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://'; $host = $_SERVER['SERVER_NAME']; $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']); $uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']); $dir = str_replace("/$file", '', $uri); return "$proto$host$port$dir"; } /** * Detects all supported databases that are compiled into PHP. * * @return * An array of database types compiled into PHP. */ function drupal_detect_database_types() { $databases = drupal_get_database_types(); foreach ($databases as $driver => $installer) { $databases[$driver] = $installer->name(); } return $databases; } /** * Returns all supported database installer objects that are compiled into PHP. * * @return * An array of database installer objects compiled into PHP. */ function drupal_get_database_types() { $databases = array(); // We define a driver as a directory in /includes/database that in turn // contains a database.inc file. That allows us to drop in additional drivers // without modifying the installer. // Because we have no registry yet, we need to also include the install.inc // file for the driver explicitly. require_once DRUPAL_ROOT . '/includes/database/database.inc'; foreach (file_scan_directory(DRUPAL_ROOT . '/includes/database', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) { if (file_exists($file->uri . '/database.inc') && file_exists($file->uri . '/install.inc')) { $drivers[$file->filename] = $file->uri; } } foreach ($drivers as $driver => $file) { $installer = db_installer_object($driver); if ($installer->installable()) { $databases[$driver] = $installer; } } // Usability: unconditionally put the MySQL driver on top. if (isset($databases['mysql'])) { $mysql_database = $databases['mysql']; unset($databases['mysql']); $databases = array('mysql' => $mysql_database) + $databases; } return $databases; } /** * Database installer structure. * * Defines basic Drupal requirements for databases. */ abstract class DatabaseTasks { /** * Structure that describes each task to run. * * @var array * * Each value of the tasks array is an associative array defining the function * to call (optional) and any arguments to be passed to the function. */ protected $tasks = array( array( 'function' => 'checkEngineVersion', 'arguments' => array(), ), array( 'arguments' => array( 'CREATE TABLE {drupal_install_test} (id int NULL)', 'Drupal can use CREATE TABLE database commands.', 'Failed to CREATE a test table on your database server with the command %query. The server reports the following message: %error.

      Are you sure the configured username has the necessary permissions to create tables in the database?

      ', TRUE, ), ), array( 'arguments' => array( 'INSERT INTO {drupal_install_test} (id) VALUES (1)', 'Drupal can use INSERT database commands.', 'Failed to INSERT a value into a test table on your database server. We tried inserting a value with the command %query and the server reported the following error: %error.', ), ), array( 'arguments' => array( 'UPDATE {drupal_install_test} SET id = 2', 'Drupal can use UPDATE database commands.', 'Failed to UPDATE a value in a test table on your database server. We tried updating a value with the command %query and the server reported the following error: %error.', ), ), array( 'arguments' => array( 'DELETE FROM {drupal_install_test}', 'Drupal can use DELETE database commands.', 'Failed to DELETE a value from a test table on your database server. We tried deleting a value with the command %query and the server reported the following error: %error.', ), ), array( 'arguments' => array( 'DROP TABLE {drupal_install_test}', 'Drupal can use DROP TABLE database commands.', 'Failed to DROP a test table from your database server. We tried dropping a table with the command %query and the server reported the following error %error.', ), ), ); /** * Results from tasks. * * @var array */ protected $results = array(); /** * Ensure the PDO driver is supported by the version of PHP in use. */ protected function hasPdoDriver() { return in_array($this->pdoDriver, PDO::getAvailableDrivers()); } /** * Assert test as failed. */ protected function fail($message) { $this->results[$message] = FALSE; } /** * Assert test as a pass. */ protected function pass($message) { $this->results[$message] = TRUE; } /** * Check whether Drupal is installable on the database. */ public function installable() { return $this->hasPdoDriver() && empty($this->error); } /** * Return the human-readable name of the driver. */ abstract public function name(); /** * Return the minimum required version of the engine. * * @return * A version string. If not NULL, it will be checked against the version * reported by the Database engine using version_compare(). */ public function minimumVersion() { return NULL; } /** * Run database tasks and tests to see if Drupal can run on the database. */ public function runTasks() { // We need to establish a connection before we can run tests. if ($this->connect()) { foreach ($this->tasks as $task) { if (!isset($task['function'])) { $task['function'] = 'runTestQuery'; } if (method_exists($this, $task['function'])) { // Returning false is fatal. No other tasks can run. if (FALSE === call_user_func_array(array($this, $task['function']), $task['arguments'])) { break; } } else { throw new DatabaseTaskException(st("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function']))); } } } // Check for failed results and compile message $message = ''; foreach ($this->results as $result => $success) { if (!$success) { $message .= '

      ' . $result . '

      '; } } if (!empty($message)) { $message = '

      In order for Drupal to work, and to continue with the installation process, you must resolve all issues reported below. For more help with configuring your database server, see the installation handbook. If you are unsure what any of this means you should probably contact your hosting provider.

      ' . $message; throw new DatabaseTaskException($message); } } /** * Check if we can connect to the database. */ protected function connect() { try { // This doesn't actually test the connection. db_set_active(); // Now actually do a check. Database::getConnection(); $this->pass('Drupal can CONNECT to the database ok.'); } catch (Exception $e) { $this->fail(st('Failed to connect to your database server. The server reports the following message: %error.
      • Is the database server running?
      • Does the database exist, and have you entered the correct database name?
      • Have you entered the correct username and password?
      • Have you entered the correct database hostname?
      ', array('%error' => $e->getMessage()))); return FALSE; } return TRUE; } /** * Run SQL tests to ensure the database can execute commands with the current user. */ protected function runTestQuery($query, $pass, $fail, $fatal = FALSE) { try { db_query($query); $this->pass(st($pass)); } catch (Exception $e) { $this->fail(st($fail, array('%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name()))); return !$fatal; } } /** * Check the engine version. */ protected function checkEngineVersion() { if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) { $this->fail(st("The database version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion()))); } } /** * Return driver specific configuration options. * * @param $database * An array of driver specific configuration options. * * @return * The options form array. */ public function getFormOptions($database) { $form['database'] = array( '#type' => 'textfield', '#title' => st('Database name'), '#default_value' => empty($database['database']) ? '' : $database['database'], '#size' => 45, '#required' => TRUE, '#description' => st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_distribution_name())), ); $form['username'] = array( '#type' => 'textfield', '#title' => st('Database username'), '#default_value' => empty($database['username']) ? '' : $database['username'], '#required' => TRUE, '#size' => 45, ); $form['password'] = array( '#type' => 'password', '#title' => st('Database password'), '#default_value' => empty($database['password']) ? '' : $database['password'], '#required' => FALSE, '#size' => 45, ); $form['advanced_options'] = array( '#type' => 'fieldset', '#title' => st('Advanced options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => st("These options are only necessary for some sites. If you're not sure what you should enter here, leave the default settings or check with your hosting provider."), '#weight' => 10, ); $profile = drupal_get_profile(); $db_prefix = ($profile == 'standard') ? 'drupal_' : $profile . '_'; $form['advanced_options']['db_prefix'] = array( '#type' => 'textfield', '#title' => st('Table prefix'), '#default_value' => '', '#size' => 45, '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_distribution_name(), '%prefix' => $db_prefix)), '#weight' => 10, ); $form['advanced_options']['host'] = array( '#type' => 'textfield', '#title' => st('Database host'), '#default_value' => empty($database['host']) ? 'localhost' : $database['host'], '#size' => 45, // Hostnames can be 255 characters long. '#maxlength' => 255, '#required' => TRUE, '#description' => st('If your database is located on a different server, change this.'), ); $form['advanced_options']['port'] = array( '#type' => 'textfield', '#title' => st('Database port'), '#default_value' => empty($database['port']) ? '' : $database['port'], '#size' => 45, // The maximum port number is 65536, 5 digits. '#maxlength' => 5, '#description' => st('If your database server is listening to a non-standard port, enter its number.'), ); return $form; } /** * Validates driver specific configuration settings. * * Checks to ensure correct basic database settings and that a proper * connection to the database can be established. * * @param $database * An array of driver specific configuration options. * * @return * An array of driver configuration errors, keyed by form element name. */ public function validateDatabaseSettings($database) { $errors = array(); // Verify the table prefix. if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) { $errors[$database['driver'] . '][advanced_options][db_prefix'] = st('The database table prefix you have entered, %prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%prefix' => $database['prefix'])); } // Verify the database port. if (!empty($database['port']) && !is_numeric($database['port'])) { $errors[$database['driver'] . '][advanced_options][port'] = st('Database port must be a number.'); } return $errors; } } /** * Exception thrown if the database installer fails. */ class DatabaseTaskException extends Exception { } /** * Replaces values in settings.php with values in the submitted array. * * @param $settings * An array of settings that need to be updated. */ function drupal_rewrite_settings($settings = array(), $prefix = '') { $default_settings = 'sites/default/default.settings.php'; drupal_static_reset('conf_path'); $settings_file = conf_path(FALSE) . '/' . $prefix . 'settings.php'; // Build list of setting names and insert the values into the global namespace. $keys = array(); foreach ($settings as $setting => $data) { $GLOBALS[$setting] = $data['value']; $keys[] = $setting; } $buffer = NULL; $first = TRUE; if ($fp = fopen(DRUPAL_ROOT . '/' . $default_settings, 'r')) { // Step line by line through settings.php. while (!feof($fp)) { $line = fgets($fp); if ($first && substr($line, 0, 5) != ' $data) { if ($data['required']) { $buffer .= "\$$setting = " . var_export($data['value'], TRUE) . ";\n"; } } $fp = fopen(DRUPAL_ROOT . '/' . $settings_file, 'w'); if ($fp && fwrite($fp, $buffer) === FALSE) { throw new Exception(st('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file))); } } else { throw new Exception(st('Failed to open %settings. Verify the file permissions.', array('%settings' => $default_settings))); } } /** * Verifies an installation profile for installation. * * @param $install_state * An array of information about the current installation state. * * @return * The list of modules to install. */ function drupal_verify_profile($install_state) { $profile = $install_state['parameters']['profile']; $locale = $install_state['parameters']['locale']; include_once DRUPAL_ROOT . '/includes/file.inc'; include_once DRUPAL_ROOT . '/includes/common.inc'; $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile"; if (!isset($profile) || !file_exists($profile_file)) { throw new Exception(install_no_profile_error()); } $info = $install_state['profile_info']; // Get a list of modules that exist in Drupal's assorted subdirectories. $present_modules = array(); foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0) as $present_module) { $present_modules[] = $present_module->name; } // The installation profile is also a module, which needs to be installed // after all the other dependencies have been installed. $present_modules[] = drupal_get_profile(); // Verify that all of the profile's required modules are present. $missing_modules = array_diff($info['dependencies'], $present_modules); $requirements = array(); if (count($missing_modules)) { $modules = array(); foreach ($missing_modules as $module) { $modules[] = '' . drupal_ucfirst($module) . ''; } $requirements['required_modules'] = array( 'title' => st('Required modules'), 'value' => st('Required modules not found.'), 'severity' => REQUIREMENT_ERROR, 'description' => st('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as sites/all/modules. Missing modules: !modules', array('!modules' => implode(', ', $modules))), ); } return $requirements; } /** * Installs the system module. * * Separated from the installation of other modules so core system * functions can be made available while other modules are installed. */ function drupal_install_system() { $system_path = drupal_get_path('module', 'system'); require_once DRUPAL_ROOT . '/' . $system_path . '/system.install'; module_invoke('system', 'install'); $system_versions = drupal_get_schema_versions('system'); $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED; db_insert('system') ->fields(array('filename', 'name', 'type', 'owner', 'status', 'schema_version', 'bootstrap')) ->values(array( 'filename' => $system_path . '/system.module', 'name' => 'system', 'type' => 'module', 'owner' => '', 'status' => 1, 'schema_version' => $system_version, 'bootstrap' => 0, )) ->execute(); system_rebuild_module_data(); } /** * Uninstalls a given list of disabled modules. * * @param array $module_list * The modules to uninstall. It is the caller's responsibility to ensure that * all modules in this list have already been disabled before this function * is called. * @param bool $uninstall_dependents * (optional) If TRUE, the function will check that all modules which depend * on the passed-in module list either are already uninstalled or contained in * the list, and it will ensure that the modules are uninstalled in the * correct order. This incurs a significant performance cost, so use FALSE if * you know $module_list is already complete and in the correct order. * Defaults to TRUE. * * @return bool * Returns TRUE if the operation succeeds or FALSE if it aborts due to an * unsafe condition, namely, $uninstall_dependents is TRUE and a module in * $module_list has dependents which are not already uninstalled and not also * included in $module_list). * * @see module_disable() */ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents = TRUE) { if ($uninstall_dependents) { // Get all module data so we can find dependents and sort. $module_data = system_rebuild_module_data(); // Create an associative array with weights as values. $module_list = array_flip(array_values($module_list)); $profile = drupal_get_profile(); while (list($module) = each($module_list)) { if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) { // This module doesn't exist or is already uninstalled. Skip it. unset($module_list[$module]); continue; } $module_list[$module] = $module_data[$module]->sort; // If the module has any dependents which are not already uninstalled and // not included in the passed-in list, abort. It is not safe to uninstall // them automatically because uninstalling a module is a destructive // operation. foreach (array_keys($module_data[$module]->required_by) as $dependent) { if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && $dependent != $profile) { return FALSE; } } } // Sort the module list by pre-calculated weights. asort($module_list); $module_list = array_keys($module_list); } foreach ($module_list as $module) { // Uninstall the module. module_load_install($module); module_invoke($module, 'uninstall'); drupal_uninstall_schema($module); watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED); } if (!empty($module_list)) { // Let other modules react. module_invoke_all('modules_uninstalled', $module_list); } return TRUE; } /** * Verifies the state of the specified file. * * @param $file * The file to check for. * @param $mask * An optional bitmask created from various FILE_* constants. * @param $type * The type of file. Can be file (default), dir, or link. * * @return * TRUE on success or FALSE on failure. A message is set for the latter. */ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { $return = TRUE; // Check for files that shouldn't be there. if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) { return FALSE; } // Verify that the file is the type of file it is supposed to be. if (isset($type) && file_exists($file)) { $check = 'is_' . $type; if (!function_exists($check) || !$check($file)) { $return = FALSE; } } // Verify file permissions. if (isset($mask)) { $masks = array(FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE); foreach ($masks as $current_mask) { if ($mask & $current_mask) { switch ($current_mask) { case FILE_EXIST: if (!file_exists($file)) { if ($type == 'dir') { drupal_install_mkdir($file, $mask); } if (!file_exists($file)) { $return = FALSE; } } break; case FILE_READABLE: if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; case FILE_WRITABLE: if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; case FILE_EXECUTABLE: if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; case FILE_NOT_READABLE: if (is_readable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; case FILE_NOT_WRITABLE: if (is_writable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; case FILE_NOT_EXECUTABLE: if (is_executable($file) && !drupal_install_fix_file($file, $mask)) { $return = FALSE; } break; } } } } return $return; } /** * Creates a directory with the specified permissions. * * @param $file * The name of the directory to create; * @param $mask * The permissions of the directory to create. * @param $message * (optional) Whether to output messages. Defaults to TRUE. * * @return * TRUE/FALSE whether or not the directory was successfully created. */ function drupal_install_mkdir($file, $mask, $message = TRUE) { $mod = 0; $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE); foreach ($masks as $m) { if ($mask & $m) { switch ($m) { case FILE_READABLE: $mod |= 0444; break; case FILE_WRITABLE: $mod |= 0222; break; case FILE_EXECUTABLE: $mod |= 0111; break; } } } if (@drupal_mkdir($file, $mod)) { return TRUE; } else { return FALSE; } } /** * Attempts to fix file permissions. * * The general approach here is that, because we do not know the security * setup of the webserver, we apply our permission changes to all three * digits of the file permission (i.e. user, group and all). * * To ensure that the values behave as expected (and numbers don't carry * from one digit to the next) we do the calculation on the octal value * using bitwise operations. This lets us remove, for example, 0222 from * 0700 and get the correct value of 0500. * * @param $file * The name of the file with permissions to fix. * @param $mask * The desired permissions for the file. * @param $message * (optional) Whether to output messages. Defaults to TRUE. * * @return * TRUE/FALSE whether or not we were able to fix the file's permissions. */ function drupal_install_fix_file($file, $mask, $message = TRUE) { // If $file does not exist, fileperms() issues a PHP warning. if (!file_exists($file)) { return FALSE; } $mod = fileperms($file) & 0777; $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE); // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings // can theoretically be 0400, 0200, and 0100 respectively, but to be safe // we set all three access types in case the administrator intends to // change the owner of settings.php after installation. foreach ($masks as $m) { if ($mask & $m) { switch ($m) { case FILE_READABLE: if (!is_readable($file)) { $mod |= 0444; } break; case FILE_WRITABLE: if (!is_writable($file)) { $mod |= 0222; } break; case FILE_EXECUTABLE: if (!is_executable($file)) { $mod |= 0111; } break; case FILE_NOT_READABLE: if (is_readable($file)) { $mod &= ~0444; } break; case FILE_NOT_WRITABLE: if (is_writable($file)) { $mod &= ~0222; } break; case FILE_NOT_EXECUTABLE: if (is_executable($file)) { $mod &= ~0111; } break; } } } // chmod() will work if the web server is running as owner of the file. // If PHP safe_mode is enabled the currently executing script must also // have the same owner. if (@chmod($file, $mod)) { return TRUE; } else { return FALSE; } } /** * Sends the user to a different installer page. * * This issues an on-site HTTP redirect. Messages (and errors) are erased. * * @param $path * An installer path. */ function install_goto($path) { global $base_url; include_once DRUPAL_ROOT . '/includes/common.inc'; header('Location: ' . $base_url . '/' . $path); header('Cache-Control: no-cache'); // Not a permanent redirect. drupal_exit(); } /** * Returns the URL of the current script, with modified query parameters. * * This function can be called by low-level scripts (such as install.php and * update.php) and returns the URL of the current script. Existing query * parameters are preserved by default, but new ones can optionally be merged * in. * * This function is used when the script must maintain certain query parameters * over multiple page requests in order to work correctly. In such cases (for * example, update.php, which requires the 'continue=1' parameter to remain in * the URL throughout the update process if there are any requirement warnings * that need to be bypassed), using this function to generate the URL for links * to the next steps of the script ensures that the links will work correctly. * * @param $query * (optional) An array of query parameters to merge in to the existing ones. * * @return * The URL of the current script, with query parameters modified by the * passed-in $query. The URL is not sanitized, so it still needs to be run * through check_url() if it will be used as an HTML attribute value. * * @see drupal_requirements_url() */ function drupal_current_script_url($query = array()) { $uri = $_SERVER['SCRIPT_NAME']; $query = array_merge(drupal_get_query_parameters(), $query); if (!empty($query)) { $uri .= '?' . drupal_http_build_query($query); } return $uri; } /** * Returns a URL for proceeding to the next page after a requirements problem. * * This function can be called by low-level scripts (such as install.php and * update.php) and returns a URL that can be used to attempt to proceed to the * next step of the script. * * @param $severity * The severity of the requirements problem, as returned by * drupal_requirements_severity(). * * @return * A URL for attempting to proceed to the next step of the script. The URL is * not sanitized, so it still needs to be run through check_url() if it will * be used as an HTML attribute value. * * @see drupal_current_script_url() */ function drupal_requirements_url($severity) { $query = array(); // If there are no errors, only warnings, append 'continue=1' to the URL so // the user can bypass this screen on the next page load. if ($severity == REQUIREMENT_WARNING) { $query['continue'] = 1; } return drupal_current_script_url($query); } /** * Translates a string when some systems are not available. * * Used during the install process, when database, theme, and localization * system is possibly not yet available. * * Use t() if your code will never run during the Drupal installation phase. * Use st() if your code will only run during installation and never any other * time. Use get_t() if your code could run in either circumstance. * * @see t() * @see get_t() * @ingroup sanitization */ function st($string, array $args = array(), array $options = array()) { static $locale_strings = NULL; global $install_state; if (empty($options['context'])) { $options['context'] = ''; } if (!isset($locale_strings)) { $locale_strings = array(); if (isset($install_state['parameters']['profile']) && isset($install_state['parameters']['locale'])) { // If the given locale was selected, there should be at least one .po file // with its name ending in {$install_state['parameters']['locale']}.po // This might or might not be the entire filename. It is also possible // that multiple files end with the same extension, even if unlikely. $po_files = file_scan_directory('./profiles/' . $install_state['parameters']['profile'] . '/translations', '/'. $install_state['parameters']['locale'] .'\.po$/', array('recurse' => FALSE)); if (count($po_files)) { require_once DRUPAL_ROOT . '/includes/locale.inc'; foreach ($po_files as $po_file) { _locale_import_read_po('mem-store', $po_file); } $locale_strings = _locale_import_one_string('mem-report'); } } } // Transform arguments before inserting them foreach ($args as $key => $value) { switch ($key[0]) { // Escaped only case '@': $args[$key] = check_plain($value); break; // Escaped and placeholder case '%': default: $args[$key] = '' . check_plain($value) . ''; break; // Pass-through case '!': } } return strtr((!empty($locale_strings[$options['context']][$string]) ? $locale_strings[$options['context']][$string] : $string), $args); } /** * Checks an installation profile's requirements. * * @param $profile * Name of installation profile to check. * @return * Array of the installation profile's requirements. */ function drupal_check_profile($profile) { include_once DRUPAL_ROOT . '/includes/file.inc'; $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile"; if (!isset($profile) || !file_exists($profile_file)) { throw new Exception(install_no_profile_error()); } $info = install_profile_info($profile); // Collect requirement testing results. $requirements = array(); foreach ($info['dependencies'] as $module) { module_load_install($module); $function = $module . '_requirements'; if (function_exists($function)) { $requirements = array_merge($requirements, $function('install')); } } return $requirements; } /** * Extracts the highest severity from the requirements array. * * @param $requirements * An array of requirements, in the same format as is returned by * hook_requirements(). * * @return * The highest severity in the array. */ function drupal_requirements_severity(&$requirements) { $severity = REQUIREMENT_OK; foreach ($requirements as $requirement) { if (isset($requirement['severity'])) { $severity = max($severity, $requirement['severity']); } } return $severity; } /** * Checks a module's requirements. * * @param $module * Machine name of module to check. * * @return * TRUE or FALSE, depending on whether the requirements are met. */ function drupal_check_module($module) { module_load_install($module); if (module_hook($module, 'requirements')) { // Check requirements $requirements = module_invoke($module, 'requirements', 'install'); if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) { // Print any error messages foreach ($requirements as $requirement) { if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) { $message = $requirement['description']; if (isset($requirement['value']) && $requirement['value']) { $message .= ' (' . t('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) . ')'; } drupal_set_message($message, 'error'); } } return FALSE; } } return TRUE; } /** * Retrieves information about an installation profile from its .info file. * * The information stored in a profile .info file is similar to that stored in * a normal Drupal module .info file. For example: * - name: The real name of the installation profile for display purposes. * - description: A brief description of the profile. * - dependencies: An array of shortnames of other modules that this install * profile requires. * * Additional, less commonly-used information that can appear in a profile.info * file but not in a normal Drupal module .info file includes: * - distribution_name: The name of the Drupal distribution that is being * installed, to be shown throughout the installation process. Defaults to * 'Drupal'. * - exclusive: If the install profile is intended to be the only eligible * choice in a distribution, setting exclusive = TRUE will auto-select it * during installation, and the install profile selection screen will be * skipped. If more than one profile is found where exclusive = TRUE then * this property will have no effect and the profile selection screen will * be shown as normal with all available profiles shown. * * Note that this function does an expensive file system scan to get info file * information for dependencies. If you only need information from the info * file itself, use system_get_info(). * * Example of .info file: * @code * name = Minimal * description = Start fresh, with only a few modules enabled. * dependencies[] = block * dependencies[] = dblog * @endcode * * @param $profile * Name of profile. * @param $locale * Name of locale used (if any). * * @return * The info array. */ function install_profile_info($profile, $locale = 'en') { $cache = &drupal_static(__FUNCTION__, array()); if (!isset($cache[$profile])) { // Set defaults for module info. $defaults = array( 'dependencies' => array(), 'description' => '', 'distribution_name' => 'Drupal', 'version' => NULL, 'hidden' => FALSE, 'php' => DRUPAL_MINIMUM_PHP, ); $info = drupal_parse_info_file("profiles/$profile/$profile.info") + $defaults; $info['dependencies'] = array_unique(array_merge( drupal_required_modules(), $info['dependencies'], ($locale != 'en' && !empty($locale) ? array('locale') : array())) ); // drupal_required_modules() includes the current profile as a dependency. // Since a module can't depend on itself we remove that element of the array. array_shift($info['dependencies']); $cache[$profile] = $info; } return $cache[$profile]; } /** * Ensures the environment for a Drupal database on a predefined connection. * * This will run tasks that check that Drupal can perform all of the functions * on a database, that Drupal needs. Tasks include simple checks like CREATE * TABLE to database specific functions like stored procedures and client * encoding. */ function db_run_tasks($driver) { db_installer_object($driver)->runTasks(); return TRUE; } /** * Returns a database installer object. * * @param $driver * The name of the driver. */ function db_installer_object($driver) { Database::loadDriverFile($driver, array('install.inc')); $task_class = 'DatabaseTasks_' . $driver; return new $task_class(); } drupal-7.26/includes/iso.inc0000644001412200141220000003615212265562324015322 0ustar benderbender country name pairs. * * Get an array of all country code => country name pairs as laid out * in ISO 3166-1 alpha-2. * Grabbed from location project (http://drupal.org/project/location). * @return * An array of all country code => country name pairs. */ function _country_get_predefined_list() { static $countries; if (isset($countries)) { return $countries; } $t = get_t(); $countries = array( 'AD' => $t('Andorra'), 'AE' => $t('United Arab Emirates'), 'AF' => $t('Afghanistan'), 'AG' => $t('Antigua and Barbuda'), 'AI' => $t('Anguilla'), 'AL' => $t('Albania'), 'AM' => $t('Armenia'), 'AN' => $t('Netherlands Antilles'), 'AO' => $t('Angola'), 'AQ' => $t('Antarctica'), 'AR' => $t('Argentina'), 'AS' => $t('American Samoa'), 'AT' => $t('Austria'), 'AU' => $t('Australia'), 'AW' => $t('Aruba'), 'AX' => $t('Aland Islands'), 'AZ' => $t('Azerbaijan'), 'BA' => $t('Bosnia and Herzegovina'), 'BB' => $t('Barbados'), 'BD' => $t('Bangladesh'), 'BE' => $t('Belgium'), 'BF' => $t('Burkina Faso'), 'BG' => $t('Bulgaria'), 'BH' => $t('Bahrain'), 'BI' => $t('Burundi'), 'BJ' => $t('Benin'), 'BL' => $t('Saint Barthélemy'), 'BM' => $t('Bermuda'), 'BN' => $t('Brunei'), 'BO' => $t('Bolivia'), 'BR' => $t('Brazil'), 'BS' => $t('Bahamas'), 'BT' => $t('Bhutan'), 'BV' => $t('Bouvet Island'), 'BW' => $t('Botswana'), 'BY' => $t('Belarus'), 'BZ' => $t('Belize'), 'CA' => $t('Canada'), 'CC' => $t('Cocos (Keeling) Islands'), 'CD' => $t('Congo (Kinshasa)'), 'CF' => $t('Central African Republic'), 'CG' => $t('Congo (Brazzaville)'), 'CH' => $t('Switzerland'), 'CI' => $t('Ivory Coast'), 'CK' => $t('Cook Islands'), 'CL' => $t('Chile'), 'CM' => $t('Cameroon'), 'CN' => $t('China'), 'CO' => $t('Colombia'), 'CR' => $t('Costa Rica'), 'CU' => $t('Cuba'), 'CW' => $t('Curaçao'), 'CV' => $t('Cape Verde'), 'CX' => $t('Christmas Island'), 'CY' => $t('Cyprus'), 'CZ' => $t('Czech Republic'), 'DE' => $t('Germany'), 'DJ' => $t('Djibouti'), 'DK' => $t('Denmark'), 'DM' => $t('Dominica'), 'DO' => $t('Dominican Republic'), 'DZ' => $t('Algeria'), 'EC' => $t('Ecuador'), 'EE' => $t('Estonia'), 'EG' => $t('Egypt'), 'EH' => $t('Western Sahara'), 'ER' => $t('Eritrea'), 'ES' => $t('Spain'), 'ET' => $t('Ethiopia'), 'FI' => $t('Finland'), 'FJ' => $t('Fiji'), 'FK' => $t('Falkland Islands'), 'FM' => $t('Micronesia'), 'FO' => $t('Faroe Islands'), 'FR' => $t('France'), 'GA' => $t('Gabon'), 'GB' => $t('United Kingdom'), 'GD' => $t('Grenada'), 'GE' => $t('Georgia'), 'GF' => $t('French Guiana'), 'GG' => $t('Guernsey'), 'GH' => $t('Ghana'), 'GI' => $t('Gibraltar'), 'GL' => $t('Greenland'), 'GM' => $t('Gambia'), 'GN' => $t('Guinea'), 'GP' => $t('Guadeloupe'), 'GQ' => $t('Equatorial Guinea'), 'GR' => $t('Greece'), 'GS' => $t('South Georgia and the South Sandwich Islands'), 'GT' => $t('Guatemala'), 'GU' => $t('Guam'), 'GW' => $t('Guinea-Bissau'), 'GY' => $t('Guyana'), 'HK' => $t('Hong Kong S.A.R., China'), 'HM' => $t('Heard Island and McDonald Islands'), 'HN' => $t('Honduras'), 'HR' => $t('Croatia'), 'HT' => $t('Haiti'), 'HU' => $t('Hungary'), 'ID' => $t('Indonesia'), 'IE' => $t('Ireland'), 'IL' => $t('Israel'), 'IM' => $t('Isle of Man'), 'IN' => $t('India'), 'IO' => $t('British Indian Ocean Territory'), 'IQ' => $t('Iraq'), 'IR' => $t('Iran'), 'IS' => $t('Iceland'), 'IT' => $t('Italy'), 'JE' => $t('Jersey'), 'JM' => $t('Jamaica'), 'JO' => $t('Jordan'), 'JP' => $t('Japan'), 'KE' => $t('Kenya'), 'KG' => $t('Kyrgyzstan'), 'KH' => $t('Cambodia'), 'KI' => $t('Kiribati'), 'KM' => $t('Comoros'), 'KN' => $t('Saint Kitts and Nevis'), 'KP' => $t('North Korea'), 'KR' => $t('South Korea'), 'KW' => $t('Kuwait'), 'KY' => $t('Cayman Islands'), 'KZ' => $t('Kazakhstan'), 'LA' => $t('Laos'), 'LB' => $t('Lebanon'), 'LC' => $t('Saint Lucia'), 'LI' => $t('Liechtenstein'), 'LK' => $t('Sri Lanka'), 'LR' => $t('Liberia'), 'LS' => $t('Lesotho'), 'LT' => $t('Lithuania'), 'LU' => $t('Luxembourg'), 'LV' => $t('Latvia'), 'LY' => $t('Libya'), 'MA' => $t('Morocco'), 'MC' => $t('Monaco'), 'MD' => $t('Moldova'), 'ME' => $t('Montenegro'), 'MF' => $t('Saint Martin (French part)'), 'MG' => $t('Madagascar'), 'MH' => $t('Marshall Islands'), 'MK' => $t('Macedonia'), 'ML' => $t('Mali'), 'MM' => $t('Myanmar'), 'MN' => $t('Mongolia'), 'MO' => $t('Macao S.A.R., China'), 'MP' => $t('Northern Mariana Islands'), 'MQ' => $t('Martinique'), 'MR' => $t('Mauritania'), 'MS' => $t('Montserrat'), 'MT' => $t('Malta'), 'MU' => $t('Mauritius'), 'MV' => $t('Maldives'), 'MW' => $t('Malawi'), 'MX' => $t('Mexico'), 'MY' => $t('Malaysia'), 'MZ' => $t('Mozambique'), 'NA' => $t('Namibia'), 'NC' => $t('New Caledonia'), 'NE' => $t('Niger'), 'NF' => $t('Norfolk Island'), 'NG' => $t('Nigeria'), 'NI' => $t('Nicaragua'), 'NL' => $t('Netherlands'), 'NO' => $t('Norway'), 'NP' => $t('Nepal'), 'NR' => $t('Nauru'), 'NU' => $t('Niue'), 'NZ' => $t('New Zealand'), 'OM' => $t('Oman'), 'PA' => $t('Panama'), 'PE' => $t('Peru'), 'PF' => $t('French Polynesia'), 'PG' => $t('Papua New Guinea'), 'PH' => $t('Philippines'), 'PK' => $t('Pakistan'), 'PL' => $t('Poland'), 'PM' => $t('Saint Pierre and Miquelon'), 'PN' => $t('Pitcairn'), 'PR' => $t('Puerto Rico'), 'PS' => $t('Palestinian Territory'), 'PT' => $t('Portugal'), 'PW' => $t('Palau'), 'PY' => $t('Paraguay'), 'QA' => $t('Qatar'), 'RE' => $t('Reunion'), 'RO' => $t('Romania'), 'RS' => $t('Serbia'), 'RU' => $t('Russia'), 'RW' => $t('Rwanda'), 'SA' => $t('Saudi Arabia'), 'SB' => $t('Solomon Islands'), 'SC' => $t('Seychelles'), 'SD' => $t('Sudan'), 'SE' => $t('Sweden'), 'SG' => $t('Singapore'), 'SH' => $t('Saint Helena'), 'SI' => $t('Slovenia'), 'SJ' => $t('Svalbard and Jan Mayen'), 'SK' => $t('Slovakia'), 'SL' => $t('Sierra Leone'), 'SM' => $t('San Marino'), 'SN' => $t('Senegal'), 'SO' => $t('Somalia'), 'SR' => $t('Suriname'), 'ST' => $t('Sao Tome and Principe'), 'SV' => $t('El Salvador'), 'SY' => $t('Syria'), 'SZ' => $t('Swaziland'), 'TC' => $t('Turks and Caicos Islands'), 'TD' => $t('Chad'), 'TF' => $t('French Southern Territories'), 'TG' => $t('Togo'), 'TH' => $t('Thailand'), 'TJ' => $t('Tajikistan'), 'TK' => $t('Tokelau'), 'TL' => $t('Timor-Leste'), 'TM' => $t('Turkmenistan'), 'TN' => $t('Tunisia'), 'TO' => $t('Tonga'), 'TR' => $t('Turkey'), 'TT' => $t('Trinidad and Tobago'), 'TV' => $t('Tuvalu'), 'TW' => $t('Taiwan'), 'TZ' => $t('Tanzania'), 'UA' => $t('Ukraine'), 'UG' => $t('Uganda'), 'UM' => $t('United States Minor Outlying Islands'), 'US' => $t('United States'), 'UY' => $t('Uruguay'), 'UZ' => $t('Uzbekistan'), 'VA' => $t('Vatican'), 'VC' => $t('Saint Vincent and the Grenadines'), 'VE' => $t('Venezuela'), 'VG' => $t('British Virgin Islands'), 'VI' => $t('U.S. Virgin Islands'), 'VN' => $t('Vietnam'), 'VU' => $t('Vanuatu'), 'WF' => $t('Wallis and Futuna'), 'WS' => $t('Samoa'), 'YE' => $t('Yemen'), 'YT' => $t('Mayotte'), 'ZA' => $t('South Africa'), 'ZM' => $t('Zambia'), 'ZW' => $t('Zimbabwe'), ); // Sort the list. natcasesort($countries); return $countries; } /** * @ingroup locale-api-predefined List of predefined languages * @{ */ /** * Some of the common languages with their English and native names * * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html */ function _locale_get_predefined_list() { return array( 'aa' => array('Afar'), 'ab' => array('Abkhazian', 'аҧсуа бызшәа'), 'ae' => array('Avestan'), 'af' => array('Afrikaans'), 'ak' => array('Akan'), 'am' => array('Amharic', 'አማርኛ'), 'ar' => array('Arabic', /* Left-to-right marker "‭" */ 'العربية', LANGUAGE_RTL), 'as' => array('Assamese'), 'ast' => array('Asturian'), 'av' => array('Avar'), 'ay' => array('Aymara'), 'az' => array('Azerbaijani', 'azərbaycan'), 'ba' => array('Bashkir'), 'be' => array('Belarusian', 'Беларуская'), 'bg' => array('Bulgarian', 'Български'), 'bh' => array('Bihari'), 'bi' => array('Bislama'), 'bm' => array('Bambara', 'Bamanankan'), 'bn' => array('Bengali'), 'bo' => array('Tibetan'), 'br' => array('Breton'), 'bs' => array('Bosnian', 'Bosanski'), 'ca' => array('Catalan', 'Català'), 'ce' => array('Chechen'), 'ch' => array('Chamorro'), 'co' => array('Corsican'), 'cr' => array('Cree'), 'cs' => array('Czech', 'Čeština'), 'cu' => array('Old Slavonic'), 'cv' => array('Chuvash'), 'cy' => array('Welsh', 'Cymraeg'), 'da' => array('Danish', 'Dansk'), 'de' => array('German', 'Deutsch'), 'dv' => array('Maldivian'), 'dz' => array('Bhutani'), 'ee' => array('Ewe', 'Ɛʋɛ'), 'el' => array('Greek', 'Ελληνικά'), 'en' => array('English'), 'en-gb' => array('English, British'), 'eo' => array('Esperanto'), 'es' => array('Spanish', 'Español'), 'et' => array('Estonian', 'Eesti'), 'eu' => array('Basque', 'Euskera'), 'fa' => array('Persian', /* Left-to-right marker "‭" */ 'فارسی', LANGUAGE_RTL), 'ff' => array('Fulah', 'Fulfulde'), 'fi' => array('Finnish', 'Suomi'), 'fil' => array('Filipino'), 'fj' => array('Fiji'), 'fo' => array('Faeroese'), 'fr' => array('French', 'Français'), 'fy' => array('Frisian', 'Frysk'), 'ga' => array('Irish', 'Gaeilge'), 'gd' => array('Scots Gaelic'), 'gl' => array('Galician', 'Galego'), 'gn' => array('Guarani'), 'gsw-berne' => array('Swiss German'), 'gu' => array('Gujarati'), 'gv' => array('Manx'), 'ha' => array('Hausa'), 'he' => array('Hebrew', /* Left-to-right marker "‭" */ 'עברית', LANGUAGE_RTL), 'hi' => array('Hindi', 'हिन्दी'), 'ho' => array('Hiri Motu'), 'hr' => array('Croatian', 'Hrvatski'), 'ht' => array('Haitian Creole'), 'hu' => array('Hungarian', 'Magyar'), 'hy' => array('Armenian', 'Հայերեն'), 'hz' => array('Herero'), 'ia' => array('Interlingua'), 'id' => array('Indonesian', 'Bahasa Indonesia'), 'ie' => array('Interlingue'), 'ig' => array('Igbo'), 'ik' => array('Inupiak'), 'is' => array('Icelandic', 'Íslenska'), 'it' => array('Italian', 'Italiano'), 'iu' => array('Inuktitut'), 'ja' => array('Japanese', '日本語'), 'jv' => array('Javanese'), 'ka' => array('Georgian'), 'kg' => array('Kongo'), 'ki' => array('Kikuyu'), 'kj' => array('Kwanyama'), 'kk' => array('Kazakh', 'Қазақ'), 'kl' => array('Greenlandic'), 'km' => array('Cambodian'), 'kn' => array('Kannada', 'ಕನ್ನಡ'), 'ko' => array('Korean', '한국어'), 'kr' => array('Kanuri'), 'ks' => array('Kashmiri'), 'ku' => array('Kurdish', 'Kurdî'), 'kv' => array('Komi'), 'kw' => array('Cornish'), 'ky' => array('Kyrgyz', 'Кыргызча'), 'la' => array('Latin', 'Latina'), 'lb' => array('Luxembourgish'), 'lg' => array('Luganda'), 'ln' => array('Lingala'), 'lo' => array('Laothian'), 'lt' => array('Lithuanian', 'Lietuvių'), 'lv' => array('Latvian', 'Latviešu'), 'mg' => array('Malagasy'), 'mh' => array('Marshallese'), 'mi' => array('Māori'), 'mk' => array('Macedonian', 'Македонски'), 'ml' => array('Malayalam', 'മലയാളം'), 'mn' => array('Mongolian'), 'mo' => array('Moldavian'), 'mr' => array('Marathi'), 'ms' => array('Malay', 'Bahasa Melayu'), 'mt' => array('Maltese', 'Malti'), 'my' => array('Burmese'), 'na' => array('Nauru'), 'nd' => array('North Ndebele'), 'ne' => array('Nepali'), 'ng' => array('Ndonga'), 'nl' => array('Dutch', 'Nederlands'), 'nb' => array('Norwegian Bokmål', 'Bokmål'), 'nn' => array('Norwegian Nynorsk', 'Nynorsk'), 'nr' => array('South Ndebele'), 'nv' => array('Navajo'), 'ny' => array('Chichewa'), 'oc' => array('Occitan'), 'om' => array('Oromo'), 'or' => array('Oriya'), 'os' => array('Ossetian'), 'pa' => array('Punjabi'), 'pi' => array('Pali'), 'pl' => array('Polish', 'Polski'), 'ps' => array('Pashto', /* Left-to-right marker "‭" */ 'پښتو', LANGUAGE_RTL), 'pt' => array('Portuguese, International'), 'pt-pt' => array('Portuguese, Portugal', 'Português'), 'pt-br' => array('Portuguese, Brazil', 'Português'), 'qu' => array('Quechua'), 'rm' => array('Rhaeto-Romance'), 'rn' => array('Kirundi'), 'ro' => array('Romanian', 'Română'), 'ru' => array('Russian', 'Русский'), 'rw' => array('Kinyarwanda'), 'sa' => array('Sanskrit'), 'sc' => array('Sardinian'), 'sco' => array('Scots'), 'sd' => array('Sindhi'), 'se' => array('Northern Sami'), 'sg' => array('Sango'), 'sh' => array('Serbo-Croatian'), 'si' => array('Sinhala', 'සිංහල'), 'sk' => array('Slovak', 'Slovenčina'), 'sl' => array('Slovenian', 'Slovenščina'), 'sm' => array('Samoan'), 'sn' => array('Shona'), 'so' => array('Somali'), 'sq' => array('Albanian', 'Shqip'), 'sr' => array('Serbian', 'Српски'), 'ss' => array('Siswati'), 'st' => array('Sesotho'), 'su' => array('Sudanese'), 'sv' => array('Swedish', 'Svenska'), 'sw' => array('Swahili', 'Kiswahili'), 'ta' => array('Tamil', 'தமிழ்'), 'te' => array('Telugu', 'తెలుగు'), 'tg' => array('Tajik'), 'th' => array('Thai', 'ภาษาไทย'), 'ti' => array('Tigrinya'), 'tk' => array('Turkmen'), 'tl' => array('Tagalog'), 'tn' => array('Setswana'), 'to' => array('Tonga'), 'tr' => array('Turkish', 'Türkçe'), 'ts' => array('Tsonga'), 'tt' => array('Tatar', 'Tatarça'), 'tw' => array('Twi'), 'ty' => array('Tahitian'), 'ug' => array('Uyghur'), 'uk' => array('Ukrainian', 'Українська'), 'ur' => array('Urdu', /* Left-to-right marker "‭" */ 'اردو', LANGUAGE_RTL), 'uz' => array('Uzbek', "o'zbek"), 've' => array('Venda'), 'vi' => array('Vietnamese', 'Tiếng Việt'), 'wo' => array('Wolof'), 'xh' => array('Xhosa', 'isiXhosa'), 'xx-lolspeak' => array('Lolspeak'), 'yi' => array('Yiddish'), 'yo' => array('Yoruba', 'Yorùbá'), 'za' => array('Zhuang'), 'zh-hans' => array('Chinese, Simplified', '简体中文'), 'zh-hant' => array('Chinese, Traditional', '繁體中文'), 'zu' => array('Zulu', 'isiZulu'), ); } /** * @} End of "locale-api-languages-predefined" */ drupal-7.26/includes/locale.inc0000644001412200141220000024412612265562324015771 0ustar benderbenderlanguage) ? $language->language : FALSE; } /** * Identify language from the Accept-language HTTP header we got. * * We perform browser accept-language parsing only if page cache is disabled, * otherwise we would cache a user-specific preference. * * @param $languages * An array of language objects for enabled languages ordered by weight. * * @return * A valid language code on success, FALSE otherwise. */ function locale_language_from_browser($languages) { if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { return FALSE; } // The Accept-Language header contains information about the language // preferences configured in the user's browser / operating system. // RFC 2616 (section 14.4) defines the Accept-Language header as follows: // Accept-Language = "Accept-Language" ":" // 1#( language-range [ ";" "q" "=" qvalue ] ) // language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" ) // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" $browser_langcodes = array(); if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { // We can safely use strtolower() here, tags are ASCII. // RFC2616 mandates that the decimal part is no more than three digits, // so we multiply the qvalue by 1000 to avoid floating point comparisons. $langcode = strtolower($match[1]); $qvalue = isset($match[2]) ? (float) $match[2] : 1; $browser_langcodes[$langcode] = (int) ($qvalue * 1000); } } // We should take pristine values from the HTTP headers, but Internet Explorer // from version 7 sends only specific language tags (eg. fr-CA) without the // corresponding generic tag (fr) unless explicitly configured. In that case, // we assume that the lowest value of the specific tags is the value of the // generic language to be as close to the HTTP 1.1 spec as possible. // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx asort($browser_langcodes); foreach ($browser_langcodes as $langcode => $qvalue) { $generic_tag = strtok($langcode, '-'); if (!isset($browser_langcodes[$generic_tag])) { $browser_langcodes[$generic_tag] = $qvalue; } } // Find the enabled language with the greatest qvalue, following the rules // of RFC 2616 (section 14.4). If several languages have the same qvalue, // prefer the one with the greatest weight. $best_match_langcode = FALSE; $max_qvalue = 0; foreach ($languages as $langcode => $language) { // Language tags are case insensitive (RFC2616, sec 3.10). $langcode = strtolower($langcode); // If nothing matches below, the default qvalue is the one of the wildcard // language, if set, or is 0 (which will never match). $qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0; // Find the longest possible prefix of the browser-supplied language // ('the language-range') that matches this site language ('the language tag'). $prefix = $langcode; do { if (isset($browser_langcodes[$prefix])) { $qvalue = $browser_langcodes[$prefix]; break; } } while ($prefix = substr($prefix, 0, strrpos($prefix, '-'))); // Find the best match. if ($qvalue > $max_qvalue) { $best_match_langcode = $language->language; $max_qvalue = $qvalue; } } return $best_match_langcode; } /** * Identify language from the user preferences. * * @param $languages * An array of valid language objects. * * @return * A valid language code on success, FALSE otherwise. */ function locale_language_from_user($languages) { // User preference (only for logged users). global $user; if ($user->uid) { return $user->language; } // No language preference from the user. return FALSE; } /** * Identify language from a request/session parameter. * * @param $languages * An array of valid language objects. * * @return * A valid language code on success, FALSE otherwise. */ function locale_language_from_session($languages) { $param = variable_get('locale_language_negotiation_session_param', 'language'); // Request parameter: we need to update the session parameter only if we have // an authenticated user. if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) { global $user; if ($user->uid) { $_SESSION[$param] = $langcode; } return $langcode; } // Session parameter. if (isset($_SESSION[$param])) { return $_SESSION[$param]; } return FALSE; } /** * Identify language via URL prefix or domain. * * @param $languages * An array of valid language objects. * * @return * A valid language code on success, FALSE otherwise. */ function locale_language_from_url($languages) { $language_url = FALSE; if (!language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_URL)) { return $language_url; } switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: // $_GET['q'] might not be available at this time, because // path initialization runs after the language bootstrap phase. list($language, $_GET['q']) = language_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages); if ($language !== FALSE) { $language_url = $language->language; } break; case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: // Get only the host, not the port. $http_host= $_SERVER['HTTP_HOST']; if (strpos($http_host, ':') !== FALSE) { $http_host_tmp = explode(':', $http_host); $http_host = current($http_host_tmp); } foreach ($languages as $language) { // Skip check if the language doesn't have a domain. if ($language->domain) { // Only compare the domains not the protocols or ports. // Remove protocol and add http:// so parse_url works $host = 'http://' . str_replace(array('http://', 'https://'), '', $language->domain); $host = parse_url($host, PHP_URL_HOST); if ($http_host == $host) { $language_url = $language->language; break; } } } break; } return $language_url; } /** * Determines the language to be assigned to URLs when none is detected. * * The language negotiation process has a fallback chain that ends with the * default language provider. Each built-in language type has a separate * initialization: * - Interface language, which is the only configurable one, always gets a valid * value. If no request-specific language is detected, the default language * will be used. * - Content language merely inherits the interface language by default. * - URL language is detected from the requested URL and will be used to rewrite * URLs appearing in the page being rendered. If no language can be detected, * there are two possibilities: * - If the default language has no configured path prefix or domain, then the * default language is used. This guarantees that (missing) URL prefixes are * preserved when navigating through the site. * - If the default language has a configured path prefix or domain, a * requested URL having an empty prefix or domain is an anomaly that must be * fixed. This is done by introducing a prefix or domain in the rendered * page matching the detected interface language. * * @param $languages * (optional) An array of valid language objects. This is passed by * language_provider_invoke() to every language provider callback, but it is * not actually needed here. Defaults to NULL. * @param $language_type * (optional) The language type to fall back to. Defaults to the interface * language. * * @return * A valid language code. */ function locale_language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) { $default = language_default(); $prefix = (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) == LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX); // If the default language is not configured to convey language information, // a missing URL language information indicates that URL language should be // the default one, otherwise we fall back to an already detected language. if (($prefix && empty($default->prefix)) || (!$prefix && empty($default->domain))) { return $default->language; } else { return $GLOBALS[$language_type]->language; } } /** * Return the URL language switcher block. Translation links may be provided by * other modules. */ function locale_language_switcher_url($type, $path) { $languages = language_list('enabled'); $links = array(); foreach ($languages[1] as $language) { $links[$language->language] = array( 'href' => $path, 'title' => $language->native, 'language' => $language, 'attributes' => array('class' => array('language-link')), ); } return $links; } /** * Return the session language switcher block. */ function locale_language_switcher_session($type, $path) { drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css'); $param = variable_get('locale_language_negotiation_session_param', 'language'); $language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $GLOBALS[$type]->language; $languages = language_list('enabled'); $links = array(); $query = $_GET; unset($query['q']); foreach ($languages[1] as $language) { $langcode = $language->language; $links[$langcode] = array( 'href' => $path, 'title' => $language->native, 'attributes' => array('class' => array('language-link')), 'query' => $query, ); if ($language_query != $langcode) { $links[$langcode]['query'][$param] = $langcode; } else { $links[$langcode]['attributes']['class'][] = ' session-active'; } } return $links; } /** * Rewrite URLs for the URL language provider. */ function locale_language_url_rewrite_url(&$path, &$options) { static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['languages'] = &drupal_static(__FUNCTION__); } $languages = &$drupal_static_fast['languages']; if (!isset($languages)) { $languages = language_list('enabled'); $languages = array_flip(array_keys($languages[1])); } // Language can be passed as an option, or we go for current URL language. if (!isset($options['language'])) { global $language_url; $options['language'] = $language_url; } // We allow only enabled languages here. elseif (!isset($languages[$options['language']->language])) { unset($options['language']); return; } if (isset($options['language'])) { switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: if ($options['language']->domain) { // Ask for an absolute URL with our modified base_url. global $is_https; $url_scheme = ($is_https) ? 'https://' : 'http://'; $options['absolute'] = TRUE; // Take the domain without ports or protocols so we can apply the // protocol needed. The setting might include a protocol. // This is changed in Drupal 8 but we need to keep backwards // compatibility for Drupal 7. $host = 'http://' . str_replace(array('http://', 'https://'), '', $options['language']->domain); $host = parse_url($host, PHP_URL_HOST); // Apply the appropriate protocol to the URL. $options['base_url'] = $url_scheme . $host; if (isset($options['https']) && variable_get('https', FALSE)) { if ($options['https'] === TRUE) { $options['base_url'] = str_replace('http://', 'https://', $options['base_url']); } elseif ($options['https'] === FALSE) { $options['base_url'] = str_replace('https://', 'http://', $options['base_url']); } } } break; case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: if (!empty($options['language']->prefix)) { $options['prefix'] = $options['language']->prefix . '/'; } break; } } } /** * Rewrite URLs for the Session language provider. */ function locale_language_url_rewrite_session(&$path, &$options) { static $query_rewrite, $query_param, $query_value; // The following values are not supposed to change during a single page // request processing. if (!isset($query_rewrite)) { global $user; if (!$user->uid) { $languages = language_list('enabled'); $languages = $languages[1]; $query_param = check_plain(variable_get('locale_language_negotiation_session_param', 'language')); $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL; $query_rewrite = isset($languages[$query_value]) && language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_SESSION); } else { $query_rewrite = FALSE; } } // If the user is anonymous, the user language provider is enabled, and the // corresponding option has been set, we must preserve any explicit user // language preference even with cookies disabled. if ($query_rewrite) { if (is_string($options['query'])) { $options['query'] = drupal_get_query_array($options['query']); } if (!isset($options['query'][$query_param])) { $options['query'][$query_param] = $query_value; } } } /** * @} End of "locale-languages-negotiation" */ /** * Check that a string is safe to be added or imported as a translation. * * This test can be used to detect possibly bad translation strings. It should * not have any false positives. But it is only a test, not a transformation, * as it destroys valid HTML. We cannot reliably filter translation strings * on import because some strings are irreversibly corrupted. For example, * a & in the translation would get encoded to &amp; by filter_xss() * before being put in the database, and thus would be displayed incorrectly. * * The allowed tag list is like filter_xss_admin(), but omitting div and img as * not needed for translation and likely to cause layout issues (div) or a * possible attack vector (img). */ function locale_string_is_safe($string) { return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))); } /** * @defgroup locale-api-add Language addition API * @{ * Add a language. * * The language addition API is used to create languages and store them. */ /** * API function to add a language. * * @param $langcode * Language code. * @param $name * English name of the language * @param $native * Native name of the language * @param $direction * LANGUAGE_LTR or LANGUAGE_RTL * @param $domain * Optional custom domain name with protocol, without * trailing slash (eg. http://de.example.com). * @param $prefix * Optional path prefix for the language. Defaults to the * language code if omitted. * @param $enabled * Optionally TRUE to enable the language when created or FALSE to disable. * @param $default * Optionally set this language to be the default. */ function locale_add_language($langcode, $name = NULL, $native = NULL, $direction = LANGUAGE_LTR, $domain = '', $prefix = '', $enabled = TRUE, $default = FALSE) { // Default prefix on language code. if (empty($prefix)) { $prefix = $langcode; } // If name was not set, we add a predefined language. if (!isset($name)) { include_once DRUPAL_ROOT . '/includes/iso.inc'; $predefined = _locale_get_predefined_list(); $name = $predefined[$langcode][0]; $native = isset($predefined[$langcode][1]) ? $predefined[$langcode][1] : $predefined[$langcode][0]; $direction = isset($predefined[$langcode][2]) ? $predefined[$langcode][2] : LANGUAGE_LTR; } db_insert('languages') ->fields(array( 'language' => $langcode, 'name' => $name, 'native' => $native, 'direction' => $direction, 'domain' => $domain, 'prefix' => $prefix, 'enabled' => $enabled, )) ->execute(); // Only set it as default if enabled. if ($enabled && $default) { variable_set('language_default', (object) array('language' => $langcode, 'name' => $name, 'native' => $native, 'direction' => $direction, 'enabled' => (int) $enabled, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => $prefix, 'weight' => 0, 'javascript' => '')); } if ($enabled) { // Increment enabled language count if we are adding an enabled language. variable_set('language_count', variable_get('language_count', 1) + 1); } // Kill the static cache in language_list(). drupal_static_reset('language_list'); // Force JavaScript translation file creation for the newly added language. _locale_invalidate_js($langcode); watchdog('locale', 'The %language language (%code) has been created.', array('%language' => $name, '%code' => $langcode)); module_invoke_all('multilingual_settings_changed'); } /** * @} End of "locale-api-add" */ /** * @defgroup locale-api-import-export Translation import/export API. * @{ * Functions to import and export translations. * * These functions provide the ability to import translations from * external files and to export translations and translation templates. */ /** * Parses Gettext Portable Object file information and inserts into database * * @param $file * Drupal file object corresponding to the PO file to import. * @param $langcode * Language code. * @param $mode * Should existing translations be replaced LOCALE_IMPORT_KEEP or * LOCALE_IMPORT_OVERWRITE. * @param $group * Text group to import PO file into (eg. 'default' for interface * translations). */ function _locale_import_po($file, $langcode, $mode, $group = NULL) { // Try to allocate enough time to parse and import the data. drupal_set_time_limit(240); // Check if we have the language already in the database. if (!db_query("SELECT COUNT(language) FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchField()) { drupal_set_message(t('The language selected for import is not supported.'), 'error'); return FALSE; } // Get strings from file (returns on failure after a partial import, or on success) $status = _locale_import_read_po('db-store', $file, $mode, $langcode, $group); if ($status === FALSE) { // Error messages are set in _locale_import_read_po(). return FALSE; } // Get status information on import process. list($header_done, $additions, $updates, $deletes, $skips) = _locale_import_one_string('db-report'); if (!$header_done) { drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error'); } // Clear cache and force refresh of JavaScript translations. _locale_invalidate_js($langcode); cache_clear_all('locale:', 'cache', TRUE); // Rebuild the menu, strings may have changed. menu_rebuild(); drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes))); watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes)); if ($skips) { $skip_message = format_plural($skips, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.'); drupal_set_message($skip_message); watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING); } return TRUE; } /** * Parses Gettext Portable Object file into an array * * @param $op * Storage operation type: db-store or mem-store. * @param $file * Drupal file object corresponding to the PO file to import. * @param $mode * Should existing translations be replaced LOCALE_IMPORT_KEEP or * LOCALE_IMPORT_OVERWRITE. * @param $lang * Language code. * @param $group * Text group to import PO file into (eg. 'default' for interface * translations). */ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = 'default') { // The file will get closed by PHP on returning from this function. $fd = fopen($file->uri, 'rb'); if (!$fd) { _locale_import_message('The translation import failed, because the file %filename could not be read.', $file); return FALSE; } /* * The parser context. Can be: * - 'COMMENT' (#) * - 'MSGID' (msgid) * - 'MSGID_PLURAL' (msgid_plural) * - 'MSGCTXT' (msgctxt) * - 'MSGSTR' (msgstr or msgstr[]) * - 'MSGSTR_ARR' (msgstr_arg) */ $context = 'COMMENT'; // Current entry being read. $current = array(); // Current plurality for 'msgstr[]'. $plural = 0; // Current line. $lineno = 0; while (!feof($fd)) { // A line should not be longer than 10 * 1024. $line = fgets($fd, 10 * 1024); if ($lineno == 0) { // The first line might come with a UTF-8 BOM, which should be removed. $line = str_replace("\xEF\xBB\xBF", '', $line); } $lineno++; // Trim away the linefeed. $line = trim(strtr($line, array("\\\n" => ""))); if (!strncmp('#', $line, 1)) { // Lines starting with '#' are comments. if ($context == 'COMMENT') { // Already in comment token, insert the comment. $current['#'][] = substr($line, 1); } elseif (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { // We are currently in string token, close it out. _locale_import_one_string($op, $current, $mode, $lang, $file, $group); // Start a new entry for the comment. $current = array(); $current['#'][] = substr($line, 1); $context = 'COMMENT'; } else { // A comment following any other token is a syntax error. _locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno); return FALSE; } } elseif (!strncmp('msgid_plural', $line, 12)) { // A plural form for the current message. if ($context != 'MSGID') { // A plural form cannot be added to anything else but the id directly. _locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno); return FALSE; } // Remove 'msgid_plural' and trim away whitespace. $line = trim(substr($line, 12)); // At this point, $line should now contain only the plural form. $quoted = _locale_import_parse_quoted($line); if ($quoted === FALSE) { // The plural form must be wrapped in quotes. _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); return FALSE; } // Append the plural form to the current entry. $current['msgid'] .= "\0" . $quoted; $context = 'MSGID_PLURAL'; } elseif (!strncmp('msgid', $line, 5)) { // Starting a new message. if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { // We are currently in a message string, close it out. _locale_import_one_string($op, $current, $mode, $lang, $file, $group); // Start a new context for the id. $current = array(); } elseif ($context == 'MSGID') { // We are currently already in the context, meaning we passed an id with no data. _locale_import_message('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $file, $lineno); return FALSE; } // Remove 'msgid' and trim away whitespace. $line = trim(substr($line, 5)); // At this point, $line should now contain only the message id. $quoted = _locale_import_parse_quoted($line); if ($quoted === FALSE) { // The message id must be wrapped in quotes. _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); return FALSE; } $current['msgid'] = $quoted; $context = 'MSGID'; } elseif (!strncmp('msgctxt', $line, 7)) { // Starting a new context. if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { // We are currently in a message, start a new one. _locale_import_one_string($op, $current, $mode, $lang, $file, $group); $current = array(); } elseif (!empty($current['msgctxt'])) { // A context cannot apply to another context. _locale_import_message('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $file, $lineno); return FALSE; } // Remove 'msgctxt' and trim away whitespaces. $line = trim(substr($line, 7)); // At this point, $line should now contain the context. $quoted = _locale_import_parse_quoted($line); if ($quoted === FALSE) { // The context string must be quoted. _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); return FALSE; } $current['msgctxt'] = $quoted; $context = 'MSGCTXT'; } elseif (!strncmp('msgstr[', $line, 7)) { // A message string for a specific plurality. if (($context != 'MSGID') && ($context != 'MSGCTXT') && ($context != 'MSGID_PLURAL') && ($context != 'MSGSTR_ARR')) { // Message strings must come after msgid, msgxtxt, msgid_plural, or other msgstr[] entries. _locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno); return FALSE; } // Ensure the plurality is terminated. if (strpos($line, ']') === FALSE) { _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); return FALSE; } // Extract the plurality. $frombracket = strstr($line, '['); $plural = substr($frombracket, 1, strpos($frombracket, ']') - 1); // Skip to the next whitespace and trim away any further whitespace, bringing $line to the message data. $line = trim(strstr($line, " ")); $quoted = _locale_import_parse_quoted($line); if ($quoted === FALSE) { // The string must be quoted. _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); return FALSE; } $current['msgstr'][$plural] = $quoted; $context = 'MSGSTR_ARR'; } elseif (!strncmp("msgstr", $line, 6)) { // A string for the an id or context. if (($context != 'MSGID') && ($context != 'MSGCTXT')) { // Strings are only valid within an id or context scope. _locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno); return FALSE; } // Remove 'msgstr' and trim away away whitespaces. $line = trim(substr($line, 6)); // At this point, $line should now contain the message. $quoted = _locale_import_parse_quoted($line); if ($quoted === FALSE) { // The string must be quoted. _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); return FALSE; } $current['msgstr'] = $quoted; $context = 'MSGSTR'; } elseif ($line != '') { // Anything that is not a token may be a continuation of a previous token. $quoted = _locale_import_parse_quoted($line); if ($quoted === FALSE) { // The string must be quoted. _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); return FALSE; } // Append the string to the current context. if (($context == 'MSGID') || ($context == 'MSGID_PLURAL')) { $current['msgid'] .= $quoted; } elseif ($context == 'MSGCTXT') { $current['msgctxt'] .= $quoted; } elseif ($context == 'MSGSTR') { $current['msgstr'] .= $quoted; } elseif ($context == 'MSGSTR_ARR') { $current['msgstr'][$plural] .= $quoted; } else { // No valid context to append to. _locale_import_message('The translation file %filename contains an error: there is an unexpected string on line %line.', $file, $lineno); return FALSE; } } } // End of PO file, closed out the last entry. if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { _locale_import_one_string($op, $current, $mode, $lang, $file, $group); } elseif ($context != 'COMMENT') { _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno); return FALSE; } } /** * Sets an error message occurred during locale file parsing. * * @param $message * The message to be translated. * @param $file * Drupal file object corresponding to the PO file to import. * @param $lineno * An optional line number argument. */ function _locale_import_message($message, $file, $lineno = NULL) { $vars = array('%filename' => $file->filename); if (isset($lineno)) { $vars['%line'] = $lineno; } $t = get_t(); drupal_set_message($t($message, $vars), 'error'); } /** * Imports a string into the database * * @param $op * Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'. * @param $value * Details of the string stored. * @param $mode * Should existing translations be replaced LOCALE_IMPORT_KEEP or * LOCALE_IMPORT_OVERWRITE. * @param $lang * Language to store the string in. * @param $file * Object representation of file being imported, only required when op is * 'db-store'. * @param $group * Text group to import PO file into (eg. 'default' for interface * translations). */ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL, $group = 'default') { $report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0)); $header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE); $strings = &drupal_static(__FUNCTION__ . ':strings', array()); switch ($op) { // Return stored strings case 'mem-report': return $strings; // Store string in memory (only supports single strings) case 'mem-store': $strings[isset($value['msgctxt']) ? $value['msgctxt'] : ''][$value['msgid']] = $value['msgstr']; return; // Called at end of import to inform the user case 'db-report': return array($header_done, $report['additions'], $report['updates'], $report['deletes'], $report['skips']); // Store the string we got in the database. case 'db-store': // We got header information. if ($value['msgid'] == '') { $languages = language_list(); if (($mode != LOCALE_IMPORT_KEEP) || empty($languages[$lang]->plurals)) { // Since we only need to parse the header if we ought to update the // plural formula, only run this if we don't need to keep existing // data untouched or if we don't have an existing plural formula. $header = _locale_import_parse_header($value['msgstr']); // Get and store the plural formula if available. if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->uri)) { list($nplurals, $plural) = $p; db_update('languages') ->fields(array( 'plurals' => $nplurals, 'formula' => $plural, )) ->condition('language', $lang) ->execute(); } } $header_done = TRUE; } else { // Some real string to import. $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']); if (strpos($value['msgid'], "\0")) { // This string has plural versions. $english = explode("\0", $value['msgid'], 2); $entries = array_keys($value['msgstr']); for ($i = 3; $i <= count($entries); $i++) { $english[] = $english[1]; } $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries); $english = array_map('_locale_import_append_plural', $english, $entries); foreach ($translation as $key => $trans) { if ($key == 0) { $plid = 0; } $plid = _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english[$key], $trans, $group, $comments, $mode, $plid, $key); } } else { // A simple string to import. $english = $value['msgid']; $translation = $value['msgstr']; _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $group, $comments, $mode); } } } // end of db-store operation } /** * Import one string into the database. * * @param $report * Report array summarizing the number of changes done in the form: * array(inserts, updates, deletes). * @param $langcode * Language code to import string into. * @param $context * The context of this string. * @param $source * Source string. * @param $translation * Translation to language specified in $langcode. * @param $textgroup * Name of textgroup to store translation in. * @param $location * Location value to save with source string. * @param $mode * Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE. * @param $plid * Optional plural ID to use. * @param $plural * Optional plural value to use. * * @return * The string ID of the existing string modified or the new string added. */ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $textgroup, $location, $mode, $plid = 0, $plural = 0) { $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $source, ':context' => $context, ':textgroup' => $textgroup))->fetchField(); if (!empty($translation)) { // Skip this string unless it passes a check for dangerous code. // Text groups other than default still can contain HTML tags // (i.e. translatable blocks). if ($textgroup == "default" && !locale_string_is_safe($translation)) { $report['skips']++; $lid = 0; } elseif ($lid) { // We have this source string saved already. db_update('locales_source') ->fields(array( 'location' => $location, )) ->condition('lid', $lid) ->execute(); $exists = db_query("SELECT COUNT(lid) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField(); if (!$exists) { // No translation in this language. db_insert('locales_target') ->fields(array( 'lid' => $lid, 'language' => $langcode, 'translation' => $translation, 'plid' => $plid, 'plural' => $plural, )) ->execute(); $report['additions']++; } elseif ($mode == LOCALE_IMPORT_OVERWRITE) { // Translation exists, only overwrite if instructed. db_update('locales_target') ->fields(array( 'translation' => $translation, 'plid' => $plid, 'plural' => $plural, )) ->condition('language', $langcode) ->condition('lid', $lid) ->execute(); $report['updates']++; } } else { // No such source string in the database yet. $lid = db_insert('locales_source') ->fields(array( 'location' => $location, 'source' => $source, 'context' => (string) $context, 'textgroup' => $textgroup, )) ->execute(); db_insert('locales_target') ->fields(array( 'lid' => $lid, 'language' => $langcode, 'translation' => $translation, 'plid' => $plid, 'plural' => $plural )) ->execute(); $report['additions']++; } } elseif ($mode == LOCALE_IMPORT_OVERWRITE) { // Empty translation, remove existing if instructed. db_delete('locales_target') ->condition('language', $langcode) ->condition('lid', $lid) ->condition('plid', $plid) ->condition('plural', $plural) ->execute(); $report['deletes']++; } return $lid; } /** * Parses a Gettext Portable Object file header * * @param $header * A string containing the complete header. * * @return * An associative array of key-value pairs. */ function _locale_import_parse_header($header) { $header_parsed = array(); $lines = array_map('trim', explode("\n", $header)); foreach ($lines as $line) { if ($line) { list($tag, $contents) = explode(":", $line, 2); $header_parsed[trim($tag)] = trim($contents); } } return $header_parsed; } /** * Parses a Plural-Forms entry from a Gettext Portable Object file header * * @param $pluralforms * A string containing the Plural-Forms entry. * @param $filepath * A string containing the filepath. * * @return * An array containing the number of plurals and a * formula in PHP for computing the plural form. */ function _locale_import_parse_plural_forms($pluralforms, $filepath) { // First, delete all whitespace $pluralforms = strtr($pluralforms, array(" " => "", "\t" => "")); // Select the parts that define nplurals and plural $nplurals = strstr($pluralforms, "nplurals="); if (strpos($nplurals, ";")) { $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9); } else { return FALSE; } $plural = strstr($pluralforms, "plural="); if (strpos($plural, ";")) { $plural = substr($plural, 7, strpos($plural, ";") - 7); } else { return FALSE; } // Get PHP version of the plural formula $plural = _locale_import_parse_arithmetic($plural); if ($plural !== FALSE) { return array($nplurals, $plural); } else { drupal_set_message(t('The translation file %filepath contains an error: the plural formula could not be parsed.', array('%filepath' => $filepath)), 'error'); return FALSE; } } /** * Parses and sanitizes an arithmetic formula into a PHP expression * * While parsing, we ensure, that the operators have the right * precedence and associativity. * * @param $string * A string containing the arithmetic formula. * * @return * The PHP version of the formula. */ function _locale_import_parse_arithmetic($string) { // Operator precedence table $precedence = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8); // Right associativity $right_associativity = array("?" => 1, ":" => 1); $tokens = _locale_import_tokenize_formula($string); // Parse by converting into infix notation then back into postfix // Operator stack - holds math operators and symbols $operator_stack = array(); // Element Stack - holds data to be operated on $element_stack = array(); foreach ($tokens as $token) { $current_token = $token; // Numbers and the $n variable are simply pushed into $element_stack if (is_numeric($token)) { $element_stack[] = $current_token; } elseif ($current_token == "n") { $element_stack[] = '$n'; } elseif ($current_token == "(") { $operator_stack[] = $current_token; } elseif ($current_token == ")") { $topop = array_pop($operator_stack); while (isset($topop) && ($topop != "(")) { $element_stack[] = $topop; $topop = array_pop($operator_stack); } } elseif (!empty($precedence[$current_token])) { // If it's an operator, then pop from $operator_stack into $element_stack until the // precedence in $operator_stack is less than current, then push into $operator_stack $topop = array_pop($operator_stack); while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) { $element_stack[] = $topop; $topop = array_pop($operator_stack); } if ($topop) { $operator_stack[] = $topop; // Return element to top } $operator_stack[] = $current_token; // Parentheses are not needed } else { return FALSE; } } // Flush operator stack $topop = array_pop($operator_stack); while ($topop != NULL) { $element_stack[] = $topop; $topop = array_pop($operator_stack); } // Now extract formula from stack $previous_size = count($element_stack) + 1; while (count($element_stack) < $previous_size) { $previous_size = count($element_stack); for ($i = 2; $i < count($element_stack); $i++) { $op = $element_stack[$i]; if (!empty($precedence[$op])) { $f = ""; if ($op == ":") { $f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")"; } elseif ($op == "?") { $f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1]; } else { $f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")"; } array_splice($element_stack, $i - 2, 3, $f); break; } } } // If only one element is left, the number of operators is appropriate if (count($element_stack) == 1) { return $element_stack[0]; } else { return FALSE; } } /** * Backward compatible implementation of token_get_all() for formula parsing * * @param $string * A string containing the arithmetic formula. * * @return * The PHP version of the formula. */ function _locale_import_tokenize_formula($formula) { $formula = str_replace(" ", "", $formula); $tokens = array(); for ($i = 0; $i < strlen($formula); $i++) { if (is_numeric($formula[$i])) { $num = $formula[$i]; $j = $i + 1; while ($j < strlen($formula) && is_numeric($formula[$j])) { $num .= $formula[$j]; $j++; } $i = $j - 1; $tokens[] = $num; } elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a space $next = $formula[$i + 1]; switch ($pos) { case 1: case 2: case 3: case 4: if ($next == '=') { $tokens[] = $formula[$i] . '='; $i++; } else { $tokens[] = $formula[$i]; } break; case 5: if ($next == '&') { $tokens[] = '&&'; $i++; } else { $tokens[] = $formula[$i]; } break; case 6: if ($next == '|') { $tokens[] = '||'; $i++; } else { $tokens[] = $formula[$i]; } break; } } else { $tokens[] = $formula[$i]; } } return $tokens; } /** * Modify a string to contain proper count indices * * This is a callback function used via array_map() * * @param $entry * An array element. * @param $key * Index of the array element. */ function _locale_import_append_plural($entry, $key) { // No modifications for 0, 1 if ($key == 0 || $key == 1) { return $entry; } // First remove any possibly false indices, then add new ones $entry = preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry); return preg_replace('/(@count)/', "\\1[$key]", $entry); } /** * Generate a short, one string version of the passed comment array * * @param $comment * An array of strings containing a comment. * * @return * Short one string version of the comment. */ function _locale_import_shorten_comments($comment) { $comm = ''; while (count($comment)) { $test = $comm . substr(array_shift($comment), 1) . ', '; if (strlen($comm) < 130) { $comm = $test; } else { break; } } return trim(substr($comm, 0, -2)); } /** * Parses a string in quotes * * @param $string * A string specified with enclosing quotes. * * @return * The string parsed from inside the quotes. */ function _locale_import_parse_quoted($string) { if (substr($string, 0, 1) != substr($string, -1, 1)) { return FALSE; // Start and end quotes must be the same } $quote = substr($string, 0, 1); $string = substr($string, 1, -1); if ($quote == '"') { // Double quotes: strip slashes return stripcslashes($string); } elseif ($quote == "'") { // Simple quote: return as-is return $string; } else { return FALSE; // Unrecognized quote } } /** * @} End of "locale-api-import-export" */ /** * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and * Drupal.formatPlural() and inserts them into the database. */ function _locale_parse_js_file($filepath) { global $language; // The file path might contain a query string, so make sure we only use the // actual file. $parsed_url = drupal_parse_url($filepath); $filepath = $parsed_url['path']; // Load the JavaScript file. $file = file_get_contents($filepath); // Match all calls to Drupal.t() in an array. // Note: \s also matches newlines with the 's' modifier. preg_match_all('~ [^\w]Drupal\s*\.\s*t\s* # match "Drupal.t" with whitespace \(\s* # match "(" argument list start (' . LOCALE_JS_STRING . ')\s* # capture string argument (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture str args (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*) # optionally capture context ?)? # close optional args [,\)] # match ")" or "," to finish ~sx', $file, $t_matches); // Match all Drupal.formatPlural() calls in another array. preg_match_all('~ [^\w]Drupal\s*\.\s*formatPlural\s* # match "Drupal.formatPlural" with whitespace \( # match "(" argument list start \s*.+?\s*,\s* # match count argument (' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument ( # capture plural string argument (?: # non-capturing group to repeat string pieces (?: \' # match start of single-quoted string (?:\\\\\'|[^\'])* # match any character except unescaped single-quote @count # match "@count" (?:\\\\\'|[^\'])* # match any character except unescaped single-quote \' # match end of single-quoted string | " # match start of double-quoted string (?:\\\\"|[^"])* # match any character except unescaped double-quote @count # match "@count" (?:\\\\"|[^"])* # match any character except unescaped double-quote " # match end of double-quoted string ) (?:\s*\+\s*)? # match "+" with possible whitespace, for str concat )+ # match multiple because we supports concatenating strs )\s* # end capturing of plural string argument (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)? # optionally capture context )? [,\)] ~sx', $file, $plural_matches); $matches = array(); // Add strings from Drupal.t(). foreach ($t_matches[1] as $key => $string) { $matches[] = array( 'string' => $string, 'context' => $t_matches[2][$key], ); } // Add string from Drupal.formatPlural(). foreach ($plural_matches[1] as $key => $string) { $matches[] = array( 'string' => $string, 'context' => $plural_matches[3][$key], ); // If there is also a plural version of this string, add it to the strings array. if (isset($plural_matches[2][$key])) { $matches[] = array( 'string' => $plural_matches[2][$key], 'context' => $plural_matches[3][$key], ); } } foreach ($matches as $key => $match) { // Remove the quotes and string concatenations from the string. $string = implode('', preg_split('~(? $string, ':context' => $context))->fetchObject(); if ($source) { // We already have this source string and now have to add the location // to the location column, if this file is not yet present in there. $locations = preg_split('~\s*;\s*~', $source->location); if (!in_array($filepath, $locations)) { $locations[] = $filepath; $locations = implode('; ', $locations); // Save the new locations string to the database. db_update('locales_source') ->fields(array( 'location' => $locations, )) ->condition('lid', $source->lid) ->execute(); } } else { // We don't have the source string yet, thus we insert it into the database. db_insert('locales_source') ->fields(array( 'location' => $filepath, 'source' => $string, 'context' => $context, 'textgroup' => 'default', )) ->execute(); } } } /** * @addtogroup locale-api-import-export * @{ */ /** * Generates a structured array of all strings with translations in * $language, if given. This array can be used to generate an export * of the string in the database. * * @param $language * Language object to generate the output for, or NULL if generating * translation template. * @param $group * Text group to export PO file from (eg. 'default' for interface * translations). */ function _locale_export_get_strings($language = NULL, $group = 'default') { if (isset($language)) { $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':language' => $language->language, ':textgroup' => $group)); } else { $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':textgroup' => $group)); } $strings = array(); foreach ($result as $child) { $string = array( 'comment' => $child->location, 'source' => $child->source, 'context' => $child->context, 'translation' => isset($child->translation) ? $child->translation : '', ); if ($child->plid) { // Has a parent lid. Since we process in the order of plids, // we already have the parent in the array, so we can add the // lid to the next plural version to it. This builds a linked // list of plurals. $string['child'] = TRUE; $strings[$child->plid]['plural'] = $child->lid; } $strings[$child->lid] = $string; } return $strings; } /** * Generates the PO(T) file contents for given strings. * * @param $language * Language object to generate the output for, or NULL if generating * translation template. * @param $strings * Array of strings to export. See _locale_export_get_strings() * on how it should be formatted. * @param $header * The header portion to use for the output file. Defaults * are provided for PO and POT files. */ function _locale_export_po_generate($language = NULL, $strings = array(), $header = NULL) { global $user; if (!isset($header)) { if (isset($language)) { $header = '# ' . $language->name . ' translation of ' . variable_get('site_name', 'Drupal') . "\n"; $header .= '# Generated by ' . $user->name . ' <' . $user->mail . ">\n"; $header .= "#\n"; $header .= "msgid \"\"\n"; $header .= "msgstr \"\"\n"; $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n"; $header .= "\"PO-Revision-Date: " . date("Y-m-d H:iO") . "\\n\"\n"; $header .= "\"Last-Translator: NAME \\n\"\n"; $header .= "\"Language-Team: LANGUAGE \\n\"\n"; $header .= "\"MIME-Version: 1.0\\n\"\n"; $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; if ($language->formula && $language->plurals) { $header .= "\"Plural-Forms: nplurals=" . $language->plurals . "; plural=" . strtr($language->formula, array('$' => '')) . ";\\n\"\n"; } } else { $header = "# LANGUAGE translation of PROJECT\n"; $header .= "# Copyright (c) YEAR NAME \n"; $header .= "#\n"; $header .= "msgid \"\"\n"; $header .= "msgstr \"\"\n"; $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n"; $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n"; $header .= "\"Last-Translator: NAME \\n\"\n"; $header .= "\"Language-Team: LANGUAGE \\n\"\n"; $header .= "\"MIME-Version: 1.0\\n\"\n"; $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; $header .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n"; } } $output = $header . "\n"; foreach ($strings as $lid => $string) { // Only process non-children, children are output below their parent. if (!isset($string['child'])) { if ($string['comment']) { $output .= '#: ' . $string['comment'] . "\n"; } if (!empty($string['context'])) { $output .= 'msgctxt ' . _locale_export_string($string['context']); } $output .= 'msgid ' . _locale_export_string($string['source']); if (!empty($string['plural'])) { $plural = $string['plural']; $output .= 'msgid_plural ' . _locale_export_string($strings[$plural]['source']); if (isset($language)) { $translation = $string['translation']; for ($i = 0; $i < $language->plurals; $i++) { $output .= 'msgstr[' . $i . '] ' . _locale_export_string($translation); if ($plural) { $translation = _locale_export_remove_plural($strings[$plural]['translation']); $plural = isset($strings[$plural]['plural']) ? $strings[$plural]['plural'] : 0; } else { $translation = ''; } } } else { $output .= 'msgstr[0] ""' . "\n"; $output .= 'msgstr[1] ""' . "\n"; } } else { $output .= 'msgstr ' . _locale_export_string($string['translation']); } $output .= "\n"; } } return $output; } /** * Write a generated PO or POT file to the output. * * @param $language * Language object to generate the output for, or NULL if generating * translation template. * @param $output * The PO(T) file to output as a string. See _locale_export_generate_po() * on how it can be generated. */ function _locale_export_po($language = NULL, $output = NULL) { // Log the export event. if (isset($language)) { $filename = $language->language . '.po'; watchdog('locale', 'Exported %locale translation file: %filename.', array('%locale' => $language->name, '%filename' => $filename)); } else { $filename = 'drupal.pot'; watchdog('locale', 'Exported translation file: %filename.', array('%filename' => $filename)); } // Download the file for the client. header("Content-Disposition: attachment; filename=$filename"); header("Content-Type: text/plain; charset=utf-8"); print $output; drupal_exit(); } /** * Print out a string on multiple lines */ function _locale_export_string($str) { $stri = addcslashes($str, "\0..\37\\\""); $parts = array(); // Cut text into several lines while ($stri != "") { $i = strpos($stri, "\\n"); if ($i === FALSE) { $curstr = $stri; $stri = ""; } else { $curstr = substr($stri, 0, $i + 2); $stri = substr($stri, $i + 2); } $curparts = explode("\n", _locale_export_wrap($curstr, 70)); $parts = array_merge($parts, $curparts); } // Multiline string if (count($parts) > 1) { return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n"; } // Single line string elseif (count($parts) == 1) { return "\"$parts[0]\"\n"; } // No translation else { return "\"\"\n"; } } /** * Custom word wrapping for Portable Object (Template) files. */ function _locale_export_wrap($str, $len) { $words = explode(' ', $str); $return = array(); $cur = ""; $nstr = 1; while (count($words)) { $word = array_shift($words); if ($nstr) { $cur = $word; $nstr = 0; } elseif (strlen("$cur $word") > $len) { $return[] = $cur . " "; $cur = $word; } else { $cur = "$cur $word"; } } $return[] = $cur; return implode("\n", $return); } /** * Removes plural index information from a string */ function _locale_export_remove_plural($entry) { return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry); } /** * @} End of "locale-api-import-export" */ /** * @defgroup locale-api-seek Translation search API * @{ * Functions to search in translation files. * * These functions provide the functionality to search for specific * translations. */ /** * Perform a string search and display results in a table */ function _locale_translate_seek() { $output = ''; // We have at least one criterion to match if (!($query = _locale_translate_seek_query())) { $query = array( 'translation' => 'all', 'group' => 'all', 'language' => 'all', 'string' => '', ); } $sql_query = db_select('locales_source', 's'); $limit_language = NULL; if ($query['language'] != 'en' && $query['language'] != 'all') { $sql_query->leftJoin('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", array(':langcode' => $query['language'])); $limit_language = $query['language']; } else { $sql_query->leftJoin('locales_target', 't', 't.lid = s.lid'); } $sql_query->fields('s', array('source', 'location', 'context', 'lid', 'textgroup')); $sql_query->fields('t', array('translation', 'language')); // Compute LIKE section. switch ($query['translation']) { case 'translated': $sql_query->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE'); $sql_query->orderBy('t.translation', 'DESC'); break; case 'untranslated': $sql_query->condition(db_and() ->condition('s.source', '%' . db_like($query['string']) . '%', 'LIKE') ->isNull('t.translation') ); $sql_query->orderBy('s.source'); break; case 'all' : default: $condition = db_or() ->condition('s.source', '%' . db_like($query['string']) . '%', 'LIKE'); if ($query['language'] != 'en') { // Only search in translations if the language is not forced to English. $condition->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE'); } $sql_query->condition($condition); break; } // Add a condition on the text group. if (!empty($query['group']) && $query['group'] != 'all') { $sql_query->condition('s.textgroup', $query['group']); } $sql_query = $sql_query->extend('PagerDefault')->limit(50); $locales = $sql_query->execute(); $groups = module_invoke_all('locale', 'groups'); $header = array(t('Text group'), t('String'), t('Context'), ($limit_language) ? t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan' => '2')); $strings = array(); foreach ($locales as $locale) { if (!isset($strings[$locale->lid])) { $strings[$locale->lid] = array( 'group' => $locale->textgroup, 'languages' => array(), 'location' => $locale->location, 'source' => $locale->source, 'context' => $locale->context, ); } if (isset($locale->language)) { $strings[$locale->lid]['languages'][$locale->language] = $locale->translation; } } $rows = array(); foreach ($strings as $lid => $string) { $rows[] = array( $groups[$string['group']], array('data' => check_plain(truncate_utf8($string['source'], 150, FALSE, TRUE)) . '
      ' . $string['location'] . ''), $string['context'], array('data' => _locale_translate_language_list($string['languages'], $limit_language), 'align' => 'center'), array('data' => l(t('edit'), "admin/config/regional/translate/edit/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')), array('data' => l(t('delete'), "admin/config/regional/translate/delete/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')), ); } $output .= theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No strings available.'))); $output .= theme('pager'); return $output; } /** * Build array out of search criteria specified in request variables */ function _locale_translate_seek_query() { $query = &drupal_static(__FUNCTION__); if (!isset($query)) { $query = array(); $fields = array('string', 'language', 'translation', 'group'); foreach ($fields as $field) { if (isset($_SESSION['locale_translation_filter'][$field])) { $query[$field] = $_SESSION['locale_translation_filter'][$field]; } } } return $query; } /** * Force the JavaScript translation file(s) to be refreshed. * * This function sets a refresh flag for a specified language, or all * languages except English, if none specified. JavaScript translation * files are rebuilt (with locale_update_js_files()) the next time a * request is served in that language. * * @param $langcode * The language code for which the file needs to be refreshed. * * @return * New content of the 'javascript_parsed' variable. */ function _locale_invalidate_js($langcode = NULL) { $parsed = variable_get('javascript_parsed', array()); if (empty($langcode)) { // Invalidate all languages. $languages = language_list(); unset($languages['en']); foreach ($languages as $lcode => $data) { $parsed['refresh:' . $lcode] = 'waiting'; } } else { // Invalidate single language. $parsed['refresh:' . $langcode] = 'waiting'; } variable_set('javascript_parsed', $parsed); return $parsed; } /** * (Re-)Creates the JavaScript translation file for a language. * * @param $language * The language, the translation file should be (re)created for. */ function _locale_rebuild_js($langcode = NULL) { if (!isset($langcode)) { global $language; } else { // Get information about the locale. $languages = language_list(); $language = $languages[$langcode]; } // Construct the array for JavaScript translations. // Only add strings with a translation to the translations array. $result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup", array(':language' => $language->language, ':textgroup' => 'default')); $translations = array(); foreach ($result as $data) { $translations[$data->context][$data->source] = $data->translation; } // Construct the JavaScript file, if there are translations. $data_hash = NULL; $data = $status = ''; if (!empty($translations)) { $data = "Drupal.locale = { "; if (!empty($language->formula)) { $data .= "'pluralFormula': function (\$n) { return Number({$language->formula}); }, "; } $data .= "'strings': " . drupal_json_encode($translations) . " };"; $data_hash = drupal_hash_base64($data); } // Construct the filepath where JS translation files are stored. // There is (on purpose) no front end to edit that variable. $dir = 'public://' . variable_get('locale_js_directory', 'languages'); // Delete old file, if we have no translations anymore, or a different file to be saved. $changed_hash = $language->javascript != $data_hash; if (!empty($language->javascript) && (!$data || $changed_hash)) { file_unmanaged_delete($dir . '/' . $language->language . '_' . $language->javascript . '.js'); $language->javascript = ''; $status = 'deleted'; } // Only create a new file if the content has changed or the original file got // lost. $dest = $dir . '/' . $language->language . '_' . $data_hash . '.js'; if ($data && ($changed_hash || !file_exists($dest))) { // Ensure that the directory exists and is writable, if possible. file_prepare_directory($dir, FILE_CREATE_DIRECTORY); // Save the file. if (file_unmanaged_save_data($data, $dest)) { $language->javascript = $data_hash; // If we deleted a previous version of the file and we replace it with a // new one we have an update. if ($status == 'deleted') { $status = 'updated'; } // If the file did not exist previously and the data has changed we have // a fresh creation. elseif ($changed_hash) { $status = 'created'; } // If the data hash is unchanged the translation was lost and has to be // rebuilt. else { $status = 'rebuilt'; } } else { $language->javascript = ''; $status = 'error'; } } // Save the new JavaScript hash (or an empty value if the file just got // deleted). Act only if some operation was executed that changed the hash // code. if ($status && $changed_hash) { db_update('languages') ->fields(array( 'javascript' => $language->javascript, )) ->condition('language', $language->language) ->execute(); // Update the default language variable if the default language has been altered. // This is necessary to keep the variable consistent with the database // version of the language and to prevent checking against an outdated hash. $default_langcode = language_default('language'); if ($default_langcode == $language->language) { $default = db_query("SELECT * FROM {languages} WHERE language = :language", array(':language' => $default_langcode))->fetchObject(); variable_set('language_default', $default); } } // Log the operation and return success flag. switch ($status) { case 'updated': watchdog('locale', 'Updated JavaScript translation file for the language %language.', array('%language' => t($language->name))); return TRUE; case 'rebuilt': watchdog('locale', 'JavaScript translation file %file.js was lost.', array('%file' => $language->javascript), WATCHDOG_WARNING); // Proceed to the 'created' case as the JavaScript translation file has // been created again. case 'created': watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => t($language->name))); return TRUE; case 'deleted': watchdog('locale', 'Removed JavaScript translation file for the language %language, because no translations currently exist for that language.', array('%language' => t($language->name))); return TRUE; case 'error': watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => t($language->name)), WATCHDOG_ERROR); return FALSE; default: // No operation needed. return TRUE; } } /** * List languages in search result table */ function _locale_translate_language_list($translation, $limit_language) { // Add CSS. drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css'); $languages = language_list(); unset($languages['en']); $output = ''; foreach ($languages as $langcode => $language) { if (!$limit_language || $limit_language == $langcode) { $output .= (!empty($translation[$langcode])) ? $langcode . ' ' : "$langcode "; } } return $output; } /** * @} End of "locale-api-seek" */ /** * @defgroup locale-api-predefined List of predefined languages * @{ * API to provide a list of predefined languages. */ /** * Prepares the language code list for a select form item with only the unsupported ones */ function _locale_prepare_predefined_list() { include_once DRUPAL_ROOT . '/includes/iso.inc'; $languages = language_list(); $predefined = _locale_get_predefined_list(); foreach ($predefined as $key => $value) { if (isset($languages[$key])) { unset($predefined[$key]); continue; } // Include native name in output, if possible if (count($value) > 1) { $tname = t($value[0]); $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])"; } else { $predefined[$key] = t($value[0]); } } asort($predefined); return $predefined; } /** * @} End of "locale-api-languages-predefined" */ /** * @defgroup locale-autoimport Automatic interface translation import * @{ * Functions to create batches for importing translations. * * These functions can be used to import translations for installed * modules. */ /** * Prepare a batch to import translations for all enabled * modules in a given language. * * @param $langcode * Language code to import translations for. * @param $finished * Optional finished callback for the batch. * @param $skip * Array of component names to skip. Used in the installer for the * second pass import, when most components are already imported. * * @return * A batch structure or FALSE if no files found. */ function locale_batch_by_language($langcode, $finished = NULL, $skip = array()) { // Collect all files to import for all enabled modules and themes. $files = array(); $components = array(); $query = db_select('system', 's'); $query->fields('s', array('name', 'filename')); $query->condition('s.status', 1); if (count($skip)) { $query->condition('name', $skip, 'NOT IN'); } $result = $query->execute(); foreach ($result as $component) { // Collect all files for all components, names as $langcode.po or // with names ending with $langcode.po. This allows for filenames // like node-module.de.po to let translators use small files and // be able to import in smaller chunks. $files = array_merge($files, file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)' . $langcode . '\.po$/', array('recurse' => FALSE))); $components[] = $component->name; } return _locale_batch_build($files, $finished, $components); } /** * Prepare a batch to run when installing modules or enabling themes. * * This batch will import translations for the newly added components * in all the languages already set up on the site. * * @param $components * An array of component (theme and/or module) names to import * translations for. * @param $finished * Optional finished callback for the batch. */ function locale_batch_by_component($components, $finished = '_locale_batch_system_finished') { $files = array(); $languages = language_list('enabled'); unset($languages[1]['en']); if (count($languages[1])) { $language_list = join('|', array_keys($languages[1])); // Collect all files to import for all $components. $result = db_query("SELECT name, filename FROM {system} WHERE status = 1"); foreach ($result as $component) { if (in_array($component->name, $components)) { // Collect all files for this component in all enabled languages, named // as $langcode.po or with names ending with $langcode.po. This allows // for filenames like node-module.de.po to let translators use small // files and be able to import in smaller chunks. $files = array_merge($files, file_scan_directory(dirname($component->filename) . '/translations', '/(^|\.)(' . $language_list . ')\.po$/', array('recurse' => FALSE))); } } return _locale_batch_build($files, $finished); } return FALSE; } /** * Build a locale batch from an array of files. * * @param $files * Array of files to import. * @param $finished * Optional finished callback for the batch. * @param $components * Optional list of component names the batch covers. Used in the installer. * * @return * A batch structure. */ function _locale_batch_build($files, $finished = NULL, $components = array()) { $t = get_t(); if (count($files)) { $operations = array(); foreach ($files as $file) { // We call _locale_batch_import for every batch operation. $operations[] = array('_locale_batch_import', array($file->uri)); } $batch = array( 'operations' => $operations, 'title' => $t('Importing interface translations'), 'init_message' => $t('Starting import'), 'error_message' => $t('Error importing interface translations'), 'file' => 'includes/locale.inc', // This is not a batch API construct, but data passed along to the // installer, so we know what did we import already. '#components' => $components, ); if (isset($finished)) { $batch['finished'] = $finished; } return $batch; } return FALSE; } /** * Perform interface translation import as a batch step. * * @param $filepath * Path to a file to import. * @param $results * Contains a list of files imported. */ function _locale_batch_import($filepath, &$context) { // The filename is either {langcode}.po or {prefix}.{langcode}.po, so // we can extract the language code to use for the import from the end. if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) { $file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath); _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]); $context['results'][] = $filepath; } } /** * Finished callback of system page locale import batch. * Inform the user of translation files imported. */ function _locale_batch_system_finished($success, $results) { if ($success) { drupal_set_message(format_plural(count($results), 'One translation file imported for the newly installed modules.', '@count translation files imported for the newly installed modules.')); } } /** * Finished callback of language addition locale import batch. * Inform the user of translation files imported. */ function _locale_batch_language_finished($success, $results) { if ($success) { drupal_set_message(format_plural(count($results), 'One translation file imported for the enabled modules.', '@count translation files imported for the enabled modules.')); } } /** * @} End of "locale-autoimport" */ /** * Get list of all predefined and custom countries. * * @return * An array of all country code => country name pairs. */ function country_get_list() { include_once DRUPAL_ROOT . '/includes/iso.inc'; $countries = _country_get_predefined_list(); // Allow other modules to modify the country list. drupal_alter('countries', $countries); return $countries; } /** * Save locale specific date formats to the database. * * @param $langcode * Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g. * 'en-CA'. * @param $type * Date format type, e.g. 'short', 'medium'. * @param $format * The date format string. */ function locale_date_format_save($langcode, $type, $format) { $locale_format = array(); $locale_format['language'] = $langcode; $locale_format['type'] = $type; $locale_format['format'] = $format; $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField(); if ($is_existing) { $keys = array('type', 'language'); drupal_write_record('date_format_locale', $locale_format, $keys); } else { drupal_write_record('date_format_locale', $locale_format); } } /** * Select locale date format details from database. * * @param $languages * An array of language codes. * * @return * An array of date formats. */ function locale_get_localized_date_format($languages) { $formats = array(); // Get list of different format types. $format_types = system_get_date_types(); $short_default = variable_get('date_format_short', 'm/d/Y - H:i'); // Loop through each language until we find one with some date formats // configured. foreach ($languages as $language) { $date_formats = system_date_format_locale($language); if (!empty($date_formats)) { // We have locale-specific date formats, so check for their types. If // we're missing a type, use the default setting instead. foreach ($format_types as $type => $type_info) { // If format exists for this language, use it. if (!empty($date_formats[$type])) { $formats['date_format_' . $type] = $date_formats[$type]; } // Otherwise get default variable setting. If this is not set, default // to the short format. else { $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); } } // Return on the first match. return $formats; } } // No locale specific formats found, so use defaults. $system_types = array('short', 'medium', 'long'); // Handle system types separately as they have defaults if no variable exists. $formats['date_format_short'] = $short_default; $formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i'); $formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i'); // For non-system types, get the default setting, otherwise use the short // format. foreach ($format_types as $type => $type_info) { if (!in_array($type, $system_types)) { $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); } } return $formats; } drupal-7.26/includes/registry.inc0000644001412200141220000001443112265562324016374 0ustar benderbenderfetchAll(); // Get the list of files we are going to parse. $files = array(); foreach ($modules as &$module) { $module->info = unserialize($module->info); $dir = dirname($module->filename); // Store the module directory for use in hook_registry_files_alter(). $module->dir = $dir; if ($module->status) { // Add files for enabled modules to the registry. foreach ($module->info['files'] as $file) { $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight); } } } foreach (file_scan_directory('includes', '/\.inc$/') as $filename => $file) { $files["$filename"] = array('module' => '', 'weight' => 0); } $transaction = db_transaction(); try { // Allow modules to manually modify the list of files before the registry // parses them. The $modules array provides the .info file information, which // includes the list of files registered to each module. Any files in the // list can then be added to the list of files that the registry will parse, // or modify attributes of a file. drupal_alter('registry_files', $files, $modules); foreach (registry_get_parsed_files() as $filename => $file) { // Add the hash for those files we have already parsed. if (isset($files[$filename])) { $files[$filename]['hash'] = $file['hash']; } else { // Flush the registry of resources in files that are no longer on disc // or are in files that no installed modules require to be parsed. db_delete('registry') ->condition('filename', $filename) ->execute(); db_delete('registry_file') ->condition('filename', $filename) ->execute(); } } $parsed_files = _registry_parse_files($files); $unchanged_resources = array(); $lookup_cache = array(); if ($cache = cache_get('lookup_cache', 'cache_bootstrap')) { $lookup_cache = $cache->data; } foreach ($lookup_cache as $key => $file) { // If the file for this cached resource is carried over unchanged from // the last registry build, then we can safely re-cache it. if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) { $unchanged_resources[$key] = $file; } } module_implements('', FALSE, TRUE); _registry_check_code(REGISTRY_RESET_LOOKUP_CACHE); } catch (Exception $e) { $transaction->rollback(); watchdog_exception('registry', $e); throw $e; } // We have some unchanged resources, warm up the cache - no need to pay // for looking them up again. if (count($unchanged_resources) > 0) { cache_set('lookup_cache', $unchanged_resources, 'cache_bootstrap'); } } /** * Return the list of files in registry_file */ function registry_get_parsed_files() { $files = array(); // We want the result as a keyed array. $files = db_query("SELECT * FROM {registry_file}")->fetchAllAssoc('filename', PDO::FETCH_ASSOC); return $files; } /** * Parse all files that have changed since the registry was last built, and save their function and class listings. * * @param $files * The list of files to check and parse. */ function _registry_parse_files($files) { $parsed_files = array(); foreach ($files as $filename => $file) { if (file_exists($filename)) { $hash = hash_file('sha256', $filename); if (empty($file['hash']) || $file['hash'] != $hash) { $file['hash'] = $hash; $parsed_files[$filename] = $file; } } } foreach ($parsed_files as $filename => $file) { _registry_parse_file($filename, file_get_contents($filename), $file['module'], $file['weight']); db_merge('registry_file') ->key(array('filename' => $filename)) ->fields(array( 'hash' => $file['hash'], )) ->execute(); } return array_keys($parsed_files); } /** * Parse a file and save its function and class listings. * * @param $filename * Name of the file we are going to parse. * @param $contents * Contents of the file we are going to parse as a string. * @param $module * (optional) Name of the module this file belongs to. * @param $weight * (optional) Weight of the module. */ function _registry_parse_file($filename, $contents, $module = '', $weight = 0) { if (preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) { foreach ($matches[2] as $key => $name) { db_merge('registry') ->key(array( 'name' => $name, 'type' => $matches[1][$key], )) ->fields(array( 'filename' => $filename, 'module' => $module, 'weight' => $weight, )) ->execute(); } // Delete any resources for this file where the name is not in the list // we just merged in. db_delete('registry') ->condition('filename', $filename) ->condition('name', $matches[2], 'NOT IN') ->execute(); } } /** * @} End of "defgroup registry". */ drupal-7.26/includes/menu.inc0000644001412200141220000041743312265562324015501 0ustar benderbender $end) { // Only look at masks that are not longer than the path of interest. continue; } elseif ($i < (1 << $length)) { // We have exhausted the masks of a given length, so decrease the length. --$length; } $current = ''; for ($j = $length; $j >= 0; $j--) { // Check the bit on the $j offset. if ($i & (1 << $j)) { // Bit one means the original value. $current .= $parts[$length - $j]; } else { // Bit zero means means wildcard. $current .= '%'; } // Unless we are at offset 0, add a slash. if ($j) { $current .= '/'; } } $ancestors[] = $current; } return $ancestors; } /** * Unserializes menu data, using a map to replace path elements. * * The menu system stores various path-related information (such as the 'page * arguments' and 'access arguments' components of a menu item) in the database * using serialized arrays, where integer values in the arrays represent * arguments to be replaced by values from the path. This function first * unserializes such menu information arrays, and then does the path * replacement. * * The path replacement acts on each integer-valued element of the unserialized * menu data array ($data) using a map array ($map, which is typically an array * of path arguments) as a list of replacements. For instance, if there is an * element of $data whose value is the number 2, then it is replaced in $data * with $map[2]; non-integer values in $data are left alone. * * As an example, an unserialized $data array with elements ('node_load', 1) * represents instructions for calling the node_load() function. Specifically, * this instruction says to use the path component at index 1 as the input * parameter to node_load(). If the path is 'node/123', then $map will be the * array ('node', 123), and the returned array from this function will have * elements ('node_load', 123), since $map[1] is 123. This return value will * indicate specifically that node_load(123) is to be called to load the node * whose ID is 123 for this menu item. * * @param $data * A serialized array of menu data, as read from the database. * @param $map * A path argument array, used to replace integer values in $data; an integer * value N in $data will be replaced by value $map[N]. Typically, the $map * array is generated from a call to the arg() function. * * @return * The unserialized $data array, with path arguments replaced. */ function menu_unserialize($data, $map) { if ($data = unserialize($data)) { foreach ($data as $k => $v) { if (is_int($v)) { $data[$k] = isset($map[$v]) ? $map[$v] : ''; } } return $data; } else { return array(); } } /** * Replaces the statically cached item for a given path. * * @param $path * The path. * @param $router_item * The router item. Usually a router entry from menu_get_item() is either * modified or set to a different path. This allows the navigation block, * the page title, the breadcrumb, and the page help to be modified in one * call. */ function menu_set_item($path, $router_item) { menu_get_item($path, $router_item); } /** * Gets a router item. * * @param $path * The path; for example, 'node/5'. The function will find the corresponding * node/% item and return that. * @param $router_item * Internal use only. * * @return * The router item or, if an error occurs in _menu_translate(), FALSE. A * router item is an associative array corresponding to one row in the * menu_router table. The value corresponding to the key 'map' holds the * loaded objects. The value corresponding to the key 'access' is TRUE if the * current user can access this page. The values corresponding to the keys * 'title', 'page_arguments', 'access_arguments', and 'theme_arguments' will * be filled in based on the database values and the objects loaded. */ function menu_get_item($path = NULL, $router_item = NULL) { $router_items = &drupal_static(__FUNCTION__); if (!isset($path)) { $path = $_GET['q']; } if (isset($router_item)) { $router_items[$path] = $router_item; } if (!isset($router_items[$path])) { // Rebuild if we know it's needed, or if the menu masks are missing which // occurs rarely, likely due to a race condition of multiple rebuilds. if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) { menu_rebuild(); } $original_map = arg(NULL, $path); $parts = array_slice($original_map, 0, MENU_MAX_PARTS); $ancestors = menu_get_ancestors($parts); $router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc(); if ($router_item) { // Allow modules to alter the router item before it is translated and // checked for access. drupal_alter('menu_get_item', $router_item, $path, $original_map); $map = _menu_translate($router_item, $original_map); $router_item['original_map'] = $original_map; if ($map === FALSE) { $router_items[$path] = FALSE; return FALSE; } if ($router_item['access']) { $router_item['map'] = $map; $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts'])); $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts'])); } } $router_items[$path] = $router_item; } return $router_items[$path]; } /** * Execute the page callback associated with the current path. * * @param $path * The drupal path whose handler is to be be executed. If set to NULL, then * the current path is used. * @param $deliver * (optional) A boolean to indicate whether the content should be sent to the * browser using the appropriate delivery callback (TRUE) or whether to return * the result to the caller (FALSE). */ function menu_execute_active_handler($path = NULL, $deliver = TRUE) { // Check if site is offline. $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE; // Allow other modules to change the site status but not the path because that // would not change the global variable. hook_url_inbound_alter() can be used // to change the path. Code later will not use the $read_only_path variable. $read_only_path = !empty($path) ? $path : $_GET['q']; drupal_alter('menu_site_status', $page_callback_result, $read_only_path); // Only continue if the site status is not set. if ($page_callback_result == MENU_SITE_ONLINE) { if ($router_item = menu_get_item($path)) { if ($router_item['access']) { if ($router_item['include_file']) { require_once DRUPAL_ROOT . '/' . $router_item['include_file']; } $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); } else { $page_callback_result = MENU_ACCESS_DENIED; } } else { $page_callback_result = MENU_NOT_FOUND; } } // Deliver the result of the page callback to the browser, or if requested, // return it raw, so calling code can do more processing. if ($deliver) { $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL; drupal_deliver_page($page_callback_result, $default_delivery_callback); } else { return $page_callback_result; } } /** * Loads objects into the map as defined in the $item['load_functions']. * * @param $item * A menu router or menu link item * @param $map * An array of path arguments; for example, array('node', '5'). * * @return * Returns TRUE for success, FALSE if an object cannot be loaded. * Names of object loading functions are placed in $item['load_functions']. * Loaded objects are placed in $map[]; keys are the same as keys in the * $item['load_functions'] array. * $item['access'] is set to FALSE if an object cannot be loaded. */ function _menu_load_objects(&$item, &$map) { if ($load_functions = $item['load_functions']) { // If someone calls this function twice, then unserialize will fail. if (!is_array($load_functions)) { $load_functions = unserialize($load_functions); } $path_map = $map; foreach ($load_functions as $index => $function) { if ($function) { $value = isset($path_map[$index]) ? $path_map[$index] : ''; if (is_array($function)) { // Set up arguments for the load function. These were pulled from // 'load arguments' in the hook_menu() entry, but they need // some processing. In this case the $function is the key to the // load_function array, and the value is the list of arguments. list($function, $args) = each($function); $load_functions[$index] = $function; // Some arguments are placeholders for dynamic items to process. foreach ($args as $i => $arg) { if ($arg === '%index') { // Pass on argument index to the load function, so multiple // occurrences of the same placeholder can be identified. $args[$i] = $index; } if ($arg === '%map') { // Pass on menu map by reference. The accepting function must // also declare this as a reference if it wants to modify // the map. $args[$i] = &$map; } if (is_int($arg)) { $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : ''; } } array_unshift($args, $value); $return = call_user_func_array($function, $args); } else { $return = $function($value); } // If callback returned an error or there is no callback, trigger 404. if ($return === FALSE) { $item['access'] = FALSE; $map = FALSE; return FALSE; } $map[$index] = $return; } } $item['load_functions'] = $load_functions; } return TRUE; } /** * Checks access to a menu item using the access callback. * * @param $item * A menu router or menu link item * @param $map * An array of path arguments; for example, array('node', '5'). * * @return * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. */ function _menu_check_access(&$item, $map) { $item['access'] = FALSE; // Determine access callback, which will decide whether or not the current // user has access to this path. $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']); // Check for a TRUE or FALSE value. if (is_numeric($callback)) { $item['access'] = (bool) $callback; } else { $arguments = menu_unserialize($item['access_arguments'], $map); // As call_user_func_array is quite slow and user_access is a very common // callback, it is worth making a special case for it. if ($callback == 'user_access') { $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]); } elseif (function_exists($callback)) { $item['access'] = call_user_func_array($callback, $arguments); } } } /** * Localizes the router item title using t() or another callback. * * Translate the title and description to allow storage of English title * strings in the database, yet display of them in the language required * by the current user. * * @param $item * A menu router item or a menu link item. * @param $map * The path as an array with objects already replaced. E.g., for path * node/123 $map would be array('node', $node) where $node is the node * object for node 123. * @param $link_translate * TRUE if we are translating a menu link item; FALSE if we are * translating a menu router item. * * @return * No return value. * $item['title'] is localized according to $item['title_callback']. * If an item's callback is check_plain(), $item['options']['html'] becomes * TRUE. * $item['description'] is translated using t(). * When doing link translation and the $item['options']['attributes']['title'] * (link title attribute) matches the description, it is translated as well. */ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { $callback = $item['title_callback']; $item['localized_options'] = $item['options']; // All 'class' attributes are assumed to be an array during rendering, but // links stored in the database may use an old string value. // @todo In order to remove this code we need to implement a database update // including unserializing all existing link options and running this code // on them, as well as adding validation to menu_link_save(). if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) { $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']); } // If we are translating the title of a menu link, and its title is the same // as the corresponding router item, then we can use the title information // from the router. If it's customized, then we need to use the link title // itself; can't localize. // If we are translating a router item (tabs, page, breadcrumb), then we // can always use the information from the router item. if (!$link_translate || ($item['title'] == $item['link_title'])) { // t() is a special case. Since it is used very close to all the time, // we handle it directly instead of using indirect, slower methods. if ($callback == 't') { if (empty($item['title_arguments'])) { $item['title'] = t($item['title']); } else { $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map)); } } elseif ($callback && function_exists($callback)) { if (empty($item['title_arguments'])) { $item['title'] = $callback($item['title']); } else { $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map)); } // Avoid calling check_plain again on l() function. if ($callback == 'check_plain') { $item['localized_options']['html'] = TRUE; } } } elseif ($link_translate) { $item['title'] = $item['link_title']; } // Translate description, see the motivation above. if (!empty($item['description'])) { $original_description = $item['description']; $item['description'] = t($item['description']); if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) { $item['localized_options']['attributes']['title'] = $item['description']; } } } /** * Handles dynamic path translation and menu access control. * * When a user arrives on a page such as node/5, this function determines * what "5" corresponds to, by inspecting the page's menu path definition, * node/%node. This will call node_load(5) to load the corresponding node * object. * * It also works in reverse, to allow the display of tabs and menu items which * contain these dynamic arguments, translating node/%node to node/5. * * Translation of menu item titles and descriptions are done here to * allow for storage of English strings in the database, and translation * to the language required to generate the current page. * * @param $router_item * A menu router item * @param $map * An array of path arguments; for example, array('node', '5'). * @param $to_arg * Execute $item['to_arg_functions'] or not. Use only if you want to render a * path from the menu table, for example tabs. * * @return * Returns the map with objects loaded as defined in the * $item['load_functions']. $item['access'] becomes TRUE if the item is * accessible, FALSE otherwise. $item['href'] is set according to the map. * If an error occurs during calling the load_functions (like trying to load * a non-existent node) then this function returns FALSE. */ function _menu_translate(&$router_item, $map, $to_arg = FALSE) { if ($to_arg && !empty($router_item['to_arg_functions'])) { // Fill in missing path elements, such as the current uid. _menu_link_map_translate($map, $router_item['to_arg_functions']); } // The $path_map saves the pieces of the path as strings, while elements in // $map may be replaced with loaded objects. $path_map = $map; if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) { // An error occurred loading an object. $router_item['access'] = FALSE; return FALSE; } // Generate the link path for the page request or local tasks. $link_map = explode('/', $router_item['path']); if (isset($router_item['tab_root'])) { $tab_root_map = explode('/', $router_item['tab_root']); } if (isset($router_item['tab_parent'])) { $tab_parent_map = explode('/', $router_item['tab_parent']); } for ($i = 0; $i < $router_item['number_parts']; $i++) { if ($link_map[$i] == '%') { $link_map[$i] = $path_map[$i]; } if (isset($tab_root_map[$i]) && $tab_root_map[$i] == '%') { $tab_root_map[$i] = $path_map[$i]; } if (isset($tab_parent_map[$i]) && $tab_parent_map[$i] == '%') { $tab_parent_map[$i] = $path_map[$i]; } } $router_item['href'] = implode('/', $link_map); $router_item['tab_root_href'] = implode('/', $tab_root_map); $router_item['tab_parent_href'] = implode('/', $tab_parent_map); $router_item['options'] = array(); _menu_check_access($router_item, $map); // For performance, don't localize an item the user can't access. if ($router_item['access']) { _menu_item_localize($router_item, $map); } return $map; } /** * Translates the path elements in the map using any to_arg helper function. * * @param $map * An array of path arguments; for example, array('node', '5'). * @param $to_arg_functions * An array of helper functions; for example, array(2 => 'menu_tail_to_arg'). * * @see hook_menu() */ function _menu_link_map_translate(&$map, $to_arg_functions) { $to_arg_functions = unserialize($to_arg_functions); foreach ($to_arg_functions as $index => $function) { // Translate place-holders into real values. $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index); if (!empty($map[$index]) || isset($arg)) { $map[$index] = $arg; } else { unset($map[$index]); } } } /** * Returns a string containing the path relative to the current index. */ function menu_tail_to_arg($arg, $map, $index) { return implode('/', array_slice($map, $index)); } /** * Loads the path as one string relative to the current index. * * To use this load function, you must specify the load arguments * in the router item as: * @code * $item['load arguments'] = array('%map', '%index'); * @endcode * * @see search_menu(). */ function menu_tail_load($arg, &$map, $index) { $arg = implode('/', array_slice($map, $index)); $map = array_slice($map, 0, $index); return $arg; } /** * Provides menu link access control, translation, and argument handling. * * This function is similar to _menu_translate(), but it also does * link-specific preparation (such as always calling to_arg() functions). * * @param $item * A menu link. * @param $translate * (optional) Whether to try to translate a link containing dynamic path * argument placeholders (%) based on the menu router item of the current * path. Defaults to FALSE. Internally used for breadcrumbs. * * @return * Returns the map of path arguments with objects loaded as defined in the * $item['load_functions']. * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. * $item['href'] is generated from link_path, possibly by to_arg functions. * $item['title'] is generated from link_title, and may be localized. * $item['options'] is unserialized; it is also changed within the call here * to $item['localized_options'] by _menu_item_localize(). */ function _menu_link_translate(&$item, $translate = FALSE) { if (!is_array($item['options'])) { $item['options'] = unserialize($item['options']); } if ($item['external']) { $item['access'] = 1; $map = array(); $item['href'] = $item['link_path']; $item['title'] = $item['link_title']; $item['localized_options'] = $item['options']; } else { // Complete the path of the menu link with elements from the current path, // if it contains dynamic placeholders (%). $map = explode('/', $item['link_path']); if (strpos($item['link_path'], '%') !== FALSE) { // Invoke registered to_arg callbacks. if (!empty($item['to_arg_functions'])) { _menu_link_map_translate($map, $item['to_arg_functions']); } // Or try to derive the path argument map from the current router item, // if this $item's path is within the router item's path. This means // that if we are on the current path 'foo/%/bar/%/baz', then // menu_get_item() will have translated the menu router item for the // current path, and we can take over the argument map for a link like // 'foo/%/bar'. This inheritance is only valid for breadcrumb links. // @see _menu_tree_check_access() // @see menu_get_active_breadcrumb() elseif ($translate && ($current_router_item = menu_get_item())) { // If $translate is TRUE, then this link is in the active trail. // Only translate paths within the current path. if (strpos($current_router_item['path'], $item['link_path']) === 0) { $count = count($map); $map = array_slice($current_router_item['original_map'], 0, $count); $item['original_map'] = $map; if (isset($current_router_item['map'])) { $item['map'] = array_slice($current_router_item['map'], 0, $count); } // Reset access to check it (for the first time). unset($item['access']); } } } $item['href'] = implode('/', $map); // Skip links containing untranslated arguments. if (strpos($item['href'], '%') !== FALSE) { $item['access'] = FALSE; return FALSE; } // menu_tree_check_access() may set this ahead of time for links to nodes. if (!isset($item['access'])) { if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) { // An error occurred loading an object. $item['access'] = FALSE; return FALSE; } _menu_check_access($item, $map); } // For performance, don't localize a link the user can't access. if ($item['access']) { _menu_item_localize($item, $map, TRUE); } } // Allow other customizations - e.g. adding a page-specific query string to the // options array. For performance reasons we only invoke this hook if the link // has the 'alter' flag set in the options array. if (!empty($item['options']['alter'])) { drupal_alter('translated_menu_link', $item, $map); } return $map; } /** * Gets a loaded object from a router item. * * menu_get_object() provides access to objects loaded by the current router * item. For example, on the page node/%node, the router loads the %node object, * and calling menu_get_object() will return that. Normally, it is necessary to * specify the type of object referenced, however node is the default. * The following example tests to see whether the node being displayed is of the * "story" content type: * @code * $node = menu_get_object(); * $story = $node->type == 'story'; * @endcode * * @param $type * Type of the object. These appear in hook_menu definitions as %type. Core * provides aggregator_feed, aggregator_category, contact, filter_format, * forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the * relevant {$type}_load function for more on each. Defaults to node. * @param $position * The position of the object in the path, where the first path segment is 0. * For node/%node, the position of %node is 1, but for comment/reply/%node, * it's 2. Defaults to 1. * @param $path * See menu_get_item() for more on this. Defaults to the current path. */ function menu_get_object($type = 'node', $position = 1, $path = NULL) { $router_item = menu_get_item($path); if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type . '_load') { return $router_item['map'][$position]; } } /** * Renders a menu tree based on the current path. * * The tree is expanded based on the current path and dynamic paths are also * changed according to the defined to_arg functions (for example the 'My * account' link is changed from user/% to a link with the current user's uid). * * @param $menu_name * The name of the menu. * * @return * A structured array representing the specified menu on the current page, to * be rendered by drupal_render(). */ function menu_tree($menu_name) { $menu_output = &drupal_static(__FUNCTION__, array()); if (!isset($menu_output[$menu_name])) { $tree = menu_tree_page_data($menu_name); $menu_output[$menu_name] = menu_tree_output($tree); } return $menu_output[$menu_name]; } /** * Returns a rendered menu tree. * * The menu item's LI element is given one of the following classes: * - expanded: The menu item is showing its submenu. * - collapsed: The menu item has a submenu which is not shown. * - leaf: The menu item has no submenu. * * @param $tree * A data structure representing the tree as returned from menu_tree_data. * * @return * A structured array to be rendered by drupal_render(). */ function menu_tree_output($tree) { $build = array(); $items = array(); // Pull out just the menu links we are going to render so that we // get an accurate count for the first/last classes. foreach ($tree as $data) { if ($data['link']['access'] && !$data['link']['hidden']) { $items[] = $data; } } $router_item = menu_get_item(); $num_items = count($items); foreach ($items as $i => $data) { $class = array(); if ($i == 0) { $class[] = 'first'; } if ($i == $num_items - 1) { $class[] = 'last'; } // Set a class for the
    • -tag. Since $data['below'] may contain local // tasks, only set 'expanded' class if the link also has children within // the current menu. if ($data['link']['has_children'] && $data['below']) { $class[] = 'expanded'; } elseif ($data['link']['has_children']) { $class[] = 'collapsed'; } else { $class[] = 'leaf'; } // Set a class if the link is in the active trail. if ($data['link']['in_active_trail']) { $class[] = 'active-trail'; $data['link']['localized_options']['attributes']['class'][] = 'active-trail'; } // Normally, l() compares the href of every link with $_GET['q'] and sets // the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) { $data['link']['localized_options']['attributes']['class'][] = 'active'; } // Allow menu-specific theme overrides. $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_'); $element['#attributes']['class'] = $class; $element['#title'] = $data['link']['title']; $element['#href'] = $data['link']['href']; $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array(); $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below']; $element['#original_link'] = $data['link']; // Index using the link's unique mlid. $build[$data['link']['mlid']] = $element; } if ($build) { // Make sure drupal_render() does not re-order the links. $build['#sorted'] = TRUE; // Add the theme wrapper for outer markup. // Allow menu-specific theme overrides. $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_'); } return $build; } /** * Gets the data structure representing a named menu tree. * * Since this can be the full tree including hidden items, the data returned * may be used for generating an an admin interface or a select. * * @param $menu_name * The named menu links to return * @param $link * A fully loaded menu link, or NULL. If a link is supplied, only the * path to root will be included in the returned tree - as if this link * represented the current page in a visible menu. * @param $max_depth * Optional maximum depth of links to retrieve. Typically useful if only one * or two levels of a sub tree are needed in conjunction with a non-NULL * $link, in which case $max_depth should be greater than $link['depth']. * * @return * An tree of menu links in an array, in the order they should be rendered. */ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) { $tree = &drupal_static(__FUNCTION__, array()); // Use $mlid as a flag for whether the data being loaded is for the whole tree. $mlid = isset($link['mlid']) ? $link['mlid'] : 0; // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth. $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $GLOBALS['language']->language . ':' . (int) $max_depth; if (!isset($tree[$cid])) { // If the static variable doesn't have the data, check {cache_menu}. $cache = cache_get($cid, 'cache_menu'); if ($cache && isset($cache->data)) { // If the cache entry exists, it contains the parameters for // menu_build_tree(). $tree_parameters = $cache->data; } // If the tree data was not in the cache, build $tree_parameters. if (!isset($tree_parameters)) { $tree_parameters = array( 'min_depth' => 1, 'max_depth' => $max_depth, ); if ($mlid) { // The tree is for a single item, so we need to match the values in its // p columns and 0 (the top level) with the plid values of other links. $parents = array(0); for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { if (!empty($link["p$i"])) { $parents[] = $link["p$i"]; } } $tree_parameters['expanded'] = $parents; $tree_parameters['active_trail'] = $parents; $tree_parameters['active_trail'][] = $mlid; } // Cache the tree building parameters using the page-specific cid. cache_set($cid, $tree_parameters, 'cache_menu'); } // Build the tree using the parameters; the resulting tree will be cached // by _menu_build_tree()). $tree[$cid] = menu_build_tree($menu_name, $tree_parameters); } return $tree[$cid]; } /** * Sets the path for determining the active trail of the specified menu tree. * * This path will also affect the breadcrumbs under some circumstances. * Breadcrumbs are built using the preferred link returned by * menu_link_get_preferred(). If the preferred link is inside one of the menus * specified in calls to menu_tree_set_path(), the preferred link will be * overridden by the corresponding path returned by menu_tree_get_path(). * * Setting this path does not affect the main content; for that use * menu_set_active_item() instead. * * @param $menu_name * The name of the affected menu tree. * @param $path * The path to use when finding the active trail. */ function menu_tree_set_path($menu_name, $path = NULL) { $paths = &drupal_static(__FUNCTION__); if (isset($path)) { $paths[$menu_name] = $path; } return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL; } /** * Gets the path for determining the active trail of the specified menu tree. * * @param $menu_name * The menu name of the requested tree. * * @return * A string containing the path. If no path has been specified with * menu_tree_set_path(), NULL is returned. */ function menu_tree_get_path($menu_name) { return menu_tree_set_path($menu_name); } /** * Gets the data structure for a named menu tree, based on the current page. * * The tree order is maintained by storing each parent in an individual * field, see http://drupal.org/node/141866 for more. * * @param $menu_name * The named menu links to return. * @param $max_depth * (optional) The maximum depth of links to retrieve. * @param $only_active_trail * (optional) Whether to only return the links in the active trail (TRUE) * instead of all links on every level of the menu link tree (FALSE). Defaults * to FALSE. Internally used for breadcrumbs only. * * @return * An array of menu links, in the order they should be rendered. The array * is a list of associative arrays -- these have two keys, link and below. * link is a menu item, ready for theming as a link. Below represents the * submenu below the link if there is one, and it is a subtree that has the * same structure described for the top-level array. */ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) { $tree = &drupal_static(__FUNCTION__, array()); // Check if the active trail has been overridden for this menu tree. $active_path = menu_tree_get_path($menu_name); // Load the menu item corresponding to the current page. if ($item = menu_get_item($active_path)) { if (isset($max_depth)) { $max_depth = min($max_depth, MENU_MAX_DEPTH); } // Generate a cache ID (cid) specific for this page. $cid = 'links:' . $menu_name . ':page:' . $item['href'] . ':' . $GLOBALS['language']->language . ':' . (int) $item['access'] . ':' . (int) $max_depth; // If we are asked for the active trail only, and $menu_name has not been // built and cached for this page yet, then this likely means that it // won't be built anymore, as this function is invoked from // template_process_page(). So in order to not build a giant menu tree // that needs to be checked for access on all levels, we simply check // whether we have the menu already in cache, or otherwise, build a minimum // tree containing the breadcrumb/active trail only. // @see menu_set_active_trail() if (!isset($tree[$cid]) && $only_active_trail) { $cid .= ':trail'; } if (!isset($tree[$cid])) { // If the static variable doesn't have the data, check {cache_menu}. $cache = cache_get($cid, 'cache_menu'); if ($cache && isset($cache->data)) { // If the cache entry exists, it contains the parameters for // menu_build_tree(). $tree_parameters = $cache->data; } // If the tree data was not in the cache, build $tree_parameters. if (!isset($tree_parameters)) { $tree_parameters = array( 'min_depth' => 1, 'max_depth' => $max_depth, ); // Parent mlids; used both as key and value to ensure uniqueness. // We always want all the top-level links with plid == 0. $active_trail = array(0 => 0); // If the item for the current page is accessible, build the tree // parameters accordingly. if ($item['access']) { // Find a menu link corresponding to the current path. If $active_path // is NULL, let menu_link_get_preferred() determine the path. if ($active_link = menu_link_get_preferred($active_path, $menu_name)) { // The active link may only be taken into account to build the // active trail, if it resides in the requested menu. Otherwise, // we'd needlessly re-run _menu_build_tree() queries for every menu // on every page. if ($active_link['menu_name'] == $menu_name) { // Use all the coordinates, except the last one because there // can be no child beyond the last column. for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { if ($active_link['p' . $i]) { $active_trail[$active_link['p' . $i]] = $active_link['p' . $i]; } } // If we are asked to build links for the active trail only, skip // the entire 'expanded' handling. if ($only_active_trail) { $tree_parameters['only_active_trail'] = TRUE; } } } $parents = $active_trail; $expanded = variable_get('menu_expanded', array()); // Check whether the current menu has any links set to be expanded. if (!$only_active_trail && in_array($menu_name, $expanded)) { // Collect all the links set to be expanded, and then add all of // their children to the list as well. do { $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('menu_links', array('mlid')) ->condition('menu_name', $menu_name) ->condition('expanded', 1) ->condition('has_children', 1) ->condition('plid', $parents, 'IN') ->condition('mlid', $parents, 'NOT IN') ->execute(); $num_rows = FALSE; foreach ($result as $item) { $parents[$item['mlid']] = $item['mlid']; $num_rows = TRUE; } } while ($num_rows); } $tree_parameters['expanded'] = $parents; $tree_parameters['active_trail'] = $active_trail; } // If access is denied, we only show top-level links in menus. else { $tree_parameters['expanded'] = $active_trail; $tree_parameters['active_trail'] = $active_trail; } // Cache the tree building parameters using the page-specific cid. cache_set($cid, $tree_parameters, 'cache_menu'); } // Build the tree using the parameters; the resulting tree will be cached // by _menu_build_tree(). $tree[$cid] = menu_build_tree($menu_name, $tree_parameters); } return $tree[$cid]; } return array(); } /** * Builds a menu tree, translates links, and checks access. * * @param $menu_name * The name of the menu. * @param $parameters * (optional) An associative array of build parameters. Possible keys: * - expanded: An array of parent link ids to return only menu links that are * children of one of the plids in this list. If empty, the whole menu tree * is built, unless 'only_active_trail' is TRUE. * - active_trail: An array of mlids, representing the coordinates of the * currently active menu link. * - only_active_trail: Whether to only return links that are in the active * trail. This option is ignored, if 'expanded' is non-empty. Internally * used for breadcrumbs. * - min_depth: The minimum depth of menu links in the resulting tree. * Defaults to 1, which is the default to build a whole tree for a menu * (excluding menu container itself). * - max_depth: The maximum depth of menu links in the resulting tree. * - conditions: An associative array of custom database select query * condition key/value pairs; see _menu_build_tree() for the actual query. * * @return * A fully built menu tree. */ function menu_build_tree($menu_name, array $parameters = array()) { // Build the menu tree. $data = _menu_build_tree($menu_name, $parameters); // Check access for the current user to each item in the tree. menu_tree_check_access($data['tree'], $data['node_links']); return $data['tree']; } /** * Builds a menu tree. * * This function may be used build the data for a menu tree only, for example * to further massage the data manually before further processing happens. * menu_tree_check_access() needs to be invoked afterwards. * * @see menu_build_tree() */ function _menu_build_tree($menu_name, array $parameters = array()) { // Static cache of already built menu trees. $trees = &drupal_static(__FUNCTION__, array()); // Build the cache id; sort parents to prevent duplicate storage and remove // default parameter values. if (isset($parameters['expanded'])) { sort($parameters['expanded']); } $tree_cid = 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($parameters)); // If we do not have this tree in the static cache, check {cache_menu}. if (!isset($trees[$tree_cid])) { $cache = cache_get($tree_cid, 'cache_menu'); if ($cache && isset($cache->data)) { $trees[$tree_cid] = $cache->data; } } if (!isset($trees[$tree_cid])) { // Select the links from the table, and recursively build the tree. We // LEFT JOIN since there is no match in {menu_router} for an external // link. $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); $query->addTag('translatable'); $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); $query->fields('ml'); $query->fields('m', array( 'load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'delivery_callback', 'tab_parent', 'tab_root', 'title', 'title_callback', 'title_arguments', 'theme_callback', 'theme_arguments', 'type', 'description', )); for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { $query->orderBy('p' . $i, 'ASC'); } $query->condition('ml.menu_name', $menu_name); if (!empty($parameters['expanded'])) { $query->condition('ml.plid', $parameters['expanded'], 'IN'); } elseif (!empty($parameters['only_active_trail'])) { $query->condition('ml.mlid', $parameters['active_trail'], 'IN'); } $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); if ($min_depth != 1) { $query->condition('ml.depth', $min_depth, '>='); } if (isset($parameters['max_depth'])) { $query->condition('ml.depth', $parameters['max_depth'], '<='); } // Add custom query conditions, if any were passed. if (isset($parameters['conditions'])) { foreach ($parameters['conditions'] as $column => $value) { $query->condition($column, $value); } } // Build an ordered array of links using the query result object. $links = array(); foreach ($query->execute() as $item) { $links[] = $item; } $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array()); $data['tree'] = menu_tree_data($links, $active_trail, $min_depth); $data['node_links'] = array(); menu_tree_collect_node_links($data['tree'], $data['node_links']); // Cache the data, if it is not already in the cache. cache_set($tree_cid, $data, 'cache_menu'); $trees[$tree_cid] = $data; } return $trees[$tree_cid]; } /** * Collects node links from a given menu tree recursively. * * @param $tree * The menu tree you wish to collect node links from. * @param $node_links * An array in which to store the collected node links. */ function menu_tree_collect_node_links(&$tree, &$node_links) { foreach ($tree as $key => $v) { if ($tree[$key]['link']['router_path'] == 'node/%') { $nid = substr($tree[$key]['link']['link_path'], 5); if (is_numeric($nid)) { $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link']; $tree[$key]['link']['access'] = FALSE; } } if ($tree[$key]['below']) { menu_tree_collect_node_links($tree[$key]['below'], $node_links); } } } /** * Checks access and performs dynamic operations for each link in the tree. * * @param $tree * The menu tree you wish to operate on. * @param $node_links * A collection of node link references generated from $tree by * menu_tree_collect_node_links(). */ function menu_tree_check_access(&$tree, $node_links = array()) { if ($node_links) { $nids = array_keys($node_links); $select = db_select('node', 'n'); $select->addField('n', 'nid'); $select->condition('n.status', 1); $select->condition('n.nid', $nids, 'IN'); $select->addTag('node_access'); $nids = $select->execute()->fetchCol(); foreach ($nids as $nid) { foreach ($node_links[$nid] as $mlid => $link) { $node_links[$nid][$mlid]['access'] = TRUE; } } } _menu_tree_check_access($tree); } /** * Sorts the menu tree and recursively checks access for each item. */ function _menu_tree_check_access(&$tree) { $new_tree = array(); foreach ($tree as $key => $v) { $item = &$tree[$key]['link']; _menu_link_translate($item); if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) { if ($tree[$key]['below']) { _menu_tree_check_access($tree[$key]['below']); } // The weights are made a uniform 5 digits by adding 50000 as an offset. // After _menu_link_translate(), $item['title'] has the localized link title. // Adding the mlid to the end of the index insures that it is unique. $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key]; } } // Sort siblings in the tree based on the weights and localized titles. ksort($new_tree); $tree = $new_tree; } /** * Sorts and returns the built data representing a menu tree. * * @param $links * A flat array of menu links that are part of the menu. Each array element * is an associative array of information about the menu link, containing the * fields from the {menu_links} table, and optionally additional information * from the {menu_router} table, if the menu item appears in both tables. * This array must be ordered depth-first. See _menu_build_tree() for a sample * query. * @param $parents * An array of the menu link ID values that are in the path from the current * page to the root of the menu tree. * @param $depth * The minimum depth to include in the returned menu tree. * * @return * An array of menu links in the form of a tree. Each item in the tree is an * associative array containing: * - link: The menu link item from $links, with additional element * 'in_active_trail' (TRUE if the link ID was in $parents). * - below: An array containing the sub-tree of this item, where each element * is a tree item array with 'link' and 'below' elements. This array will be * empty if the menu item has no items in its sub-tree having a depth * greater than or equal to $depth. */ function menu_tree_data(array $links, array $parents = array(), $depth = 1) { // Reverse the array so we can use the more efficient array_pop() function. $links = array_reverse($links); return _menu_tree_data($links, $parents, $depth); } /** * Builds the data representing a menu tree. * * The function is a bit complex because the rendering of a link depends on * the next menu link. */ function _menu_tree_data(&$links, $parents, $depth) { $tree = array(); while ($item = array_pop($links)) { // We need to determine if we're on the path to root so we can later build // the correct active trail and breadcrumb. $item['in_active_trail'] = in_array($item['mlid'], $parents); // Add the current link to the tree. $tree[$item['mlid']] = array( 'link' => $item, 'below' => array(), ); // Look ahead to the next link, but leave it on the array so it's available // to other recursive function calls if we return or build a sub-tree. $next = end($links); // Check whether the next link is the first in a new sub-tree. if ($next && $next['depth'] > $depth) { // Recursively call _menu_tree_data to build the sub-tree. $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']); // Fetch next link after filling the sub-tree. $next = end($links); } // Determine if we should exit the loop and return. if (!$next || $next['depth'] < $depth) { break; } } return $tree; } /** * Implements template_preprocess_HOOK() for theme_menu_tree(). */ function template_preprocess_menu_tree(&$variables) { $variables['tree'] = $variables['tree']['#children']; } /** * Returns HTML for a wrapper for a menu sub-tree. * * @param $variables * An associative array containing: * - tree: An HTML string containing the tree's items. * * @see template_preprocess_menu_tree() * @ingroup themeable */ function theme_menu_tree($variables) { return ''; } /** * Returns HTML for a menu link and submenu. * * @param $variables * An associative array containing: * - element: Structured array data for a menu link. * * @ingroup themeable */ function theme_menu_link(array $variables) { $element = $variables['element']; $sub_menu = ''; if ($element['#below']) { $sub_menu = drupal_render($element['#below']); } $output = l($element['#title'], $element['#href'], $element['#localized_options']); return '' . $output . $sub_menu . "
    • \n"; } /** * Returns HTML for a single local task link. * * @param $variables * An associative array containing: * - element: A render element containing: * - #link: A menu link array with 'title', 'href', and 'localized_options' * keys. * - #active: A boolean indicating whether the local task is active. * * @ingroup themeable */ function theme_menu_local_task($variables) { $link = $variables['element']['#link']; $link_text = $link['title']; if (!empty($variables['element']['#active'])) { // Add text to indicate active tab for non-visual users. $active = '' . t('(active tab)') . ''; // If the link does not contain HTML already, check_plain() it now. // After we set 'html'=TRUE the link will not be sanitized by l(). if (empty($link['localized_options']['html'])) { $link['title'] = check_plain($link['title']); } $link['localized_options']['html'] = TRUE; $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active)); } return '' . l($link_text, $link['href'], $link['localized_options']) . "\n"; } /** * Returns HTML for a single local action link. * * @param $variables * An associative array containing: * - element: A render element containing: * - #link: A menu link array with 'title', 'href', and 'localized_options' * keys. * * @ingroup themeable */ function theme_menu_local_action($variables) { $link = $variables['element']['#link']; $output = '
    • '; if (isset($link['href'])) { $output .= l($link['title'], $link['href'], isset($link['localized_options']) ? $link['localized_options'] : array()); } elseif (!empty($link['localized_options']['html'])) { $output .= $link['title']; } else { $output .= check_plain($link['title']); } $output .= "
    • \n"; return $output; } /** * Generates elements for the $arg array in the help hook. */ function drupal_help_arg($arg = array()) { // Note - the number of empty elements should be > MENU_MAX_PARTS. return $arg + array('', '', '', '', '', '', '', '', '', '', '', ''); } /** * Returns the help associated with the active menu item. */ function menu_get_active_help() { $output = ''; $router_path = menu_tab_root_path(); // We will always have a path unless we are on a 403 or 404. if (!$router_path) { return ''; } $arg = drupal_help_arg(arg(NULL)); foreach (module_implements('help') as $module) { $function = $module . '_help'; // Lookup help for this path. if ($help = $function($router_path, $arg)) { $output .= $help . "\n"; } } return $output; } /** * Gets the custom theme for the current page, if there is one. * * @param $initialize * This parameter should only be used internally; it is set to TRUE in order * to force the custom theme to be initialized for the current page request. * * @return * The machine-readable name of the custom theme, if there is one. * * @see menu_set_custom_theme() */ function menu_get_custom_theme($initialize = FALSE) { $custom_theme = &drupal_static(__FUNCTION__); // Skip this if the site is offline or being installed or updated, since the // menu system may not be correctly initialized then. if ($initialize && !_menu_site_is_offline(TRUE) && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install'))) { // First allow modules to dynamically set a custom theme for the current // page. Since we can only have one, the last module to return a valid // theme takes precedence. $custom_themes = array_filter(module_invoke_all('custom_theme'), 'drupal_theme_access'); if (!empty($custom_themes)) { $custom_theme = array_pop($custom_themes); } // If there is a theme callback function for the current page, execute it. // If this returns a valid theme, it will override any theme that was set // by a hook_custom_theme() implementation above. $router_item = menu_get_item(); if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) { $theme_name = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']); if (drupal_theme_access($theme_name)) { $custom_theme = $theme_name; } } } return $custom_theme; } /** * Sets a custom theme for the current page, if there is one. */ function menu_set_custom_theme() { menu_get_custom_theme(TRUE); } /** * Build a list of named menus. */ function menu_get_names() { $names = &drupal_static(__FUNCTION__); if (empty($names)) { $names = db_select('menu_links') ->distinct() ->fields('menu_links', array('menu_name')) ->orderBy('menu_name') ->execute()->fetchCol(); } return $names; } /** * Returns an array containing the names of system-defined (default) menus. */ function menu_list_system_menus() { return array( 'navigation' => 'Navigation', 'management' => 'Management', 'user-menu' => 'User menu', 'main-menu' => 'Main menu', ); } /** * Returns an array of links to be rendered as the Main menu. */ function menu_main_menu() { return menu_navigation_links(variable_get('menu_main_links_source', 'main-menu')); } /** * Returns an array of links to be rendered as the Secondary links. */ function menu_secondary_menu() { // If the secondary menu source is set as the primary menu, we display the // second level of the primary menu. if (variable_get('menu_secondary_links_source', 'user-menu') == variable_get('menu_main_links_source', 'main-menu')) { return menu_navigation_links(variable_get('menu_main_links_source', 'main-menu'), 1); } else { return menu_navigation_links(variable_get('menu_secondary_links_source', 'user-menu'), 0); } } /** * Returns an array of links for a navigation menu. * * @param $menu_name * The name of the menu. * @param $level * Optional, the depth of the menu to be returned. * * @return * An array of links of the specified menu and level. */ function menu_navigation_links($menu_name, $level = 0) { // Don't even bother querying the menu table if no menu is specified. if (empty($menu_name)) { return array(); } // Get the menu hierarchy for the current page. $tree = menu_tree_page_data($menu_name, $level + 1); // Go down the active trail until the right level is reached. while ($level-- > 0 && $tree) { // Loop through the current level's items until we find one that is in trail. while ($item = array_shift($tree)) { if ($item['link']['in_active_trail']) { // If the item is in the active trail, we continue in the subtree. $tree = empty($item['below']) ? array() : $item['below']; break; } } } // Create a single level of links. $router_item = menu_get_item(); $links = array(); foreach ($tree as $item) { if (!$item['link']['hidden']) { $class = ''; $l = $item['link']['localized_options']; $l['href'] = $item['link']['href']; $l['title'] = $item['link']['title']; if ($item['link']['in_active_trail']) { $class = ' active-trail'; $l['attributes']['class'][] = 'active-trail'; } // Normally, l() compares the href of every link with $_GET['q'] and sets // the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) { $l['attributes']['class'][] = 'active'; } // Keyed with the unique mlid to generate classes in theme_links(). $links['menu-' . $item['link']['mlid'] . $class] = $l; } } return $links; } /** * Collects the local tasks (tabs), action links, and the root path. * * @param $level * The level of tasks you ask for. Primary tasks are 0, secondary are 1. * * @return * An array containing * - tabs: Local tasks for the requested level: * - count: The number of local tasks. * - output: The themed output of local tasks. * - actions: Action links for the requested level: * - count: The number of action links. * - output: The themed output of action links. * - root_path: The router path for the current page. If the current page is * a default local task, then this corresponds to the parent tab. */ function menu_local_tasks($level = 0) { $data = &drupal_static(__FUNCTION__); $root_path = &drupal_static(__FUNCTION__ . ':root_path', ''); $empty = array( 'tabs' => array('count' => 0, 'output' => array()), 'actions' => array('count' => 0, 'output' => array()), 'root_path' => &$root_path, ); if (!isset($data)) { $data = array(); // Set defaults in case there are no actions or tabs. $actions = $empty['actions']; $tabs = array(); $router_item = menu_get_item(); // If this router item points to its parent, start from the parents to // compute tabs and actions. if ($router_item && ($router_item['type'] & MENU_LINKS_TO_PARENT)) { $router_item = menu_get_item($router_item['tab_parent_href']); } // If we failed to fetch a router item or the current user doesn't have // access to it, don't bother computing the tabs. if (!$router_item || !$router_item['access']) { return $empty; } // Get all tabs (also known as local tasks) and the root page. $cid = 'local_tasks:' . $router_item['tab_root']; if ($cache = cache_get($cid, 'cache_menu')) { $result = $cache->data; } else { $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('menu_router') ->condition('tab_root', $router_item['tab_root']) ->condition('context', MENU_CONTEXT_INLINE, '<>') ->orderBy('weight') ->orderBy('title') ->execute() ->fetchAll(); cache_set($cid, $result, 'cache_menu'); } $map = $router_item['original_map']; $children = array(); $tasks = array(); $root_path = $router_item['path']; foreach ($result as $item) { _menu_translate($item, $map, TRUE); if ($item['tab_parent']) { // All tabs, but not the root page. $children[$item['tab_parent']][$item['path']] = $item; } // Store the translated item for later use. $tasks[$item['path']] = $item; } // Find all tabs below the current path. $path = $router_item['path']; // Tab parenting may skip levels, so the number of parts in the path may not // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort). $depth = 1001; $actions['count'] = 0; $actions['output'] = array(); while (isset($children[$path])) { $tabs_current = array(); $actions_current = array(); $next_path = ''; $tab_count = 0; $action_count = 0; foreach ($children[$path] as $item) { // Local tasks can be normal items too, so bitmask with // MENU_IS_LOCAL_TASK before checking. if (!($item['type'] & MENU_IS_LOCAL_TASK)) { // This item is not a tab, skip it. continue; } if ($item['access']) { $link = $item; // The default task is always active. As tabs can be normal items // too, so bitmask with MENU_LINKS_TO_PARENT before checking. if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) { // Find the first parent which is not a default local task or action. for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']); // Use the path of the parent instead. $link['href'] = $tasks[$p]['href']; // Mark the link as active, if the current path happens to be the // path of the default local task itself (i.e., instead of its // tab_parent_href or tab_root_href). Normally, links for default // local tasks link to their parent, but the path of default local // tasks can still be accessed directly, in which case this link // would not be marked as active, since l() only compares the href // with $_GET['q']. if ($link['href'] != $_GET['q']) { $link['localized_options']['attributes']['class'][] = 'active'; } $tabs_current[] = array( '#theme' => 'menu_local_task', '#link' => $link, '#active' => TRUE, ); $next_path = $item['path']; $tab_count++; } else { // Actions can be normal items too, so bitmask with // MENU_IS_LOCAL_ACTION before checking. if (($item['type'] & MENU_IS_LOCAL_ACTION) == MENU_IS_LOCAL_ACTION) { // The item is an action, display it as such. $actions_current[] = array( '#theme' => 'menu_local_action', '#link' => $link, ); $action_count++; } else { // Otherwise, it's a normal tab. $tabs_current[] = array( '#theme' => 'menu_local_task', '#link' => $link, ); $tab_count++; } } } } $path = $next_path; $tabs[$depth]['count'] = $tab_count; $tabs[$depth]['output'] = $tabs_current; $actions['count'] += $action_count; $actions['output'] = array_merge($actions['output'], $actions_current); $depth++; } $data['actions'] = $actions; // Find all tabs at the same level or above the current one. $parent = $router_item['tab_parent']; $path = $router_item['path']; $current = $router_item; $depth = 1000; while (isset($children[$parent])) { $tabs_current = array(); $next_path = ''; $next_parent = ''; $count = 0; foreach ($children[$parent] as $item) { // Skip local actions. if ($item['type'] & MENU_IS_LOCAL_ACTION) { continue; } if ($item['access']) { $count++; $link = $item; // Local tasks can be normal items too, so bitmask with // MENU_LINKS_TO_PARENT before checking. if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) { // Find the first parent which is not a default local task. for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']); // Use the path of the parent instead. $link['href'] = $tasks[$p]['href']; if ($item['path'] == $router_item['path']) { $root_path = $tasks[$p]['path']; } } // We check for the active tab. if ($item['path'] == $path) { // Mark the link as active, if the current path is a (second-level) // local task of a default local task. Since this default local task // links to its parent, l() will not mark it as active, as it only // compares the link's href to $_GET['q']. if ($link['href'] != $_GET['q']) { $link['localized_options']['attributes']['class'][] = 'active'; } $tabs_current[] = array( '#theme' => 'menu_local_task', '#link' => $link, '#active' => TRUE, ); $next_path = $item['tab_parent']; if (isset($tasks[$next_path])) { $next_parent = $tasks[$next_path]['tab_parent']; } } else { $tabs_current[] = array( '#theme' => 'menu_local_task', '#link' => $link, ); } } } $path = $next_path; $parent = $next_parent; $tabs[$depth]['count'] = $count; $tabs[$depth]['output'] = $tabs_current; $depth--; } // Sort by depth. ksort($tabs); // Remove the depth, we are interested only in their relative placement. $tabs = array_values($tabs); $data['tabs'] = $tabs; // Allow modules to alter local tasks or dynamically append further tasks. drupal_alter('menu_local_tasks', $data, $router_item, $root_path); } if (isset($data['tabs'][$level])) { return array( 'tabs' => $data['tabs'][$level], 'actions' => $data['actions'], 'root_path' => $root_path, ); } // @todo If there are no tabs, then there still can be actions; for example, // when added via hook_menu_local_tasks_alter(). elseif (!empty($data['actions']['output'])) { return array('actions' => $data['actions']) + $empty; } return $empty; } /** * Retrieves contextual links for a path based on registered local tasks. * * This leverages the menu system to retrieve the first layer of registered * local tasks for a given system path. All local tasks of the tab type * MENU_CONTEXT_INLINE are taken into account. * * For example, when considering the following registered local tasks: * - node/%node/view (default local task) with no 'context' defined * - node/%node/edit with context: MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE * - node/%node/revisions with context: MENU_CONTEXT_PAGE * - node/%node/report-as-spam with context: MENU_CONTEXT_INLINE * * If the path "node/123" is passed to this function, then it will return the * links for 'edit' and 'report-as-spam'. * * @param $module * The name of the implementing module. This is used to prefix the key for * each contextual link, which is transformed into a CSS class during * rendering by theme_links(). For example, if $module is 'block' and the * retrieved local task path argument is 'edit', then the resulting CSS class * will be 'block-edit'. * @param $parent_path * The static menu router path of the object to retrieve local tasks for, for * example 'node' or 'admin/structure/block/manage'. * @param $args * A list of dynamic path arguments to append to $parent_path to form the * fully-qualified menu router path; for example, array(123) for a certain * node or array('system', 'navigation') for a certain block. * * @return * A list of menu router items that are local tasks for the passed-in path. * * @see contextual_links_preprocess() * @see hook_menu() */ function menu_contextual_links($module, $parent_path, $args) { static $path_empty = array(); $links = array(); // Performance: In case a previous invocation for the same parent path did not // return any links, we immediately return here. if (isset($path_empty[$parent_path]) && strpos($parent_path, '%') !== FALSE) { return $links; } // Construct the item-specific parent path. $path = $parent_path . '/' . implode('/', $args); // Get the router item for the given parent link path. $router_item = menu_get_item($path); if (!$router_item || !$router_item['access']) { $path_empty[$parent_path] = TRUE; return $links; } $data = &drupal_static(__FUNCTION__, array()); $root_path = $router_item['path']; // Performance: For a single, normalized path (such as 'node/%') we only query // available tasks once per request. if (!isset($data[$root_path])) { // Get all contextual links that are direct children of the router item and // not of the tab type 'view'. $data[$root_path] = db_select('menu_router', 'm') ->fields('m') ->condition('tab_parent', $router_item['tab_root']) ->condition('context', MENU_CONTEXT_NONE, '<>') ->condition('context', MENU_CONTEXT_PAGE, '<>') ->orderBy('weight') ->orderBy('title') ->execute() ->fetchAllAssoc('path', PDO::FETCH_ASSOC); } $parent_length = drupal_strlen($root_path) + 1; $map = $router_item['original_map']; foreach ($data[$root_path] as $item) { // Extract the actual "task" string from the path argument. $key = drupal_substr($item['path'], $parent_length); // Denormalize and translate the contextual link. _menu_translate($item, $map, TRUE); if (!$item['access']) { continue; } // All contextual links are keyed by the actual "task" path argument, // prefixed with the name of the implementing module. $links[$module . '-' . $key] = $item; } // Allow modules to alter contextual links. drupal_alter('menu_contextual_links', $links, $router_item, $root_path); // Performance: If the current user does not have access to any links for this // router path and no other module added further links, we assign FALSE here // to skip the entire process the next time the same router path is requested. if (empty($links)) { $path_empty[$parent_path] = TRUE; } return $links; } /** * Returns the rendered local tasks at the top level. */ function menu_primary_local_tasks() { $links = menu_local_tasks(0); // Do not display single tabs. return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : ''); } /** * Returns the rendered local tasks at the second level. */ function menu_secondary_local_tasks() { $links = menu_local_tasks(1); // Do not display single tabs. return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : ''); } /** * Returns the rendered local actions at the current level. */ function menu_local_actions() { $links = menu_local_tasks(); return $links['actions']['output']; } /** * Returns the router path, or the path for a default local task's parent. */ function menu_tab_root_path() { $links = menu_local_tasks(); return $links['root_path']; } /** * Returns a renderable element for the primary and secondary tabs. */ function menu_local_tabs() { return array( '#theme' => 'menu_local_tasks', '#primary' => menu_primary_local_tasks(), '#secondary' => menu_secondary_local_tasks(), ); } /** * Returns HTML for primary and secondary local tasks. * * @param $variables * An associative array containing: * - primary: (optional) An array of local tasks (tabs). * - secondary: (optional) An array of local tasks (tabs). * * @ingroup themeable * @see menu_local_tasks() */ function theme_menu_local_tasks(&$variables) { $output = ''; if (!empty($variables['primary'])) { $variables['primary']['#prefix'] = '

      ' . t('Primary tabs') . '

      '; $variables['primary']['#prefix'] .= '
        '; $variables['primary']['#suffix'] = '
      '; $output .= drupal_render($variables['primary']); } if (!empty($variables['secondary'])) { $variables['secondary']['#prefix'] = '

      ' . t('Secondary tabs') . '

      '; $variables['secondary']['#prefix'] .= '
        '; $variables['secondary']['#suffix'] = '
      '; $output .= drupal_render($variables['secondary']); } return $output; } /** * Sets (or gets) the active menu for the current page. * * The active menu for the page determines the active trail. * * @return * An array of menu machine names, in order of preference. The * 'menu_default_active_menus' variable may be used to assert a menu order * different from the order of creation, or to prevent a particular menu from * being used at all in the active trail. * E.g., $conf['menu_default_active_menus'] = array('navigation', 'main-menu') */ function menu_set_active_menu_names($menu_names = NULL) { $active = &drupal_static(__FUNCTION__); if (isset($menu_names) && is_array($menu_names)) { $active = $menu_names; } elseif (!isset($active)) { $active = variable_get('menu_default_active_menus', array_keys(menu_list_system_menus())); } return $active; } /** * Gets the active menu for the current page. */ function menu_get_active_menu_names() { return menu_set_active_menu_names(); } /** * Sets the active path, which determines which page is loaded. * * Note that this may not have the desired effect unless invoked very early * in the page load, such as during hook_boot(), or unless you call * menu_execute_active_handler() to generate your page output. * * @param $path * A Drupal path - not a path alias. */ function menu_set_active_item($path) { $_GET['q'] = $path; // Since the active item has changed, the active menu trail may also be out // of date. drupal_static_reset('menu_set_active_trail'); } /** * Sets the active trail (path to the menu tree root) of the current page. * * Any trail set by this function will only be used for functionality that calls * menu_get_active_trail(). Drupal core only uses trails set here for * breadcrumbs and the page title and not for menu trees or page content. * Additionally, breadcrumbs set by drupal_set_breadcrumb() will override any * trail set here. * * To affect the trail used by menu trees, use menu_tree_set_path(). To affect * the page content, use menu_set_active_item() instead. * * @param $new_trail * Menu trail to set; the value is saved in a static variable and can be * retrieved by menu_get_active_trail(). The format of this array should be * the same as the return value of menu_get_active_trail(). * * @return * The active trail. See menu_get_active_trail() for details. */ function menu_set_active_trail($new_trail = NULL) { $trail = &drupal_static(__FUNCTION__); if (isset($new_trail)) { $trail = $new_trail; } elseif (!isset($trail)) { $trail = array(); $trail[] = array( 'title' => t('Home'), 'href' => '', 'link_path' => '', 'localized_options' => array(), 'type' => 0, ); // Try to retrieve a menu link corresponding to the current path. If more // than one exists, the link from the most preferred menu is returned. $preferred_link = menu_link_get_preferred(); $current_item = menu_get_item(); // There is a link for the current path. if ($preferred_link) { // Pass TRUE for $only_active_trail to make menu_tree_page_data() build // a stripped down menu tree containing the active trail only, in case // the given menu has not been built in this request yet. $tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE); list($key, $curr) = each($tree); } // There is no link for the current path. else { $preferred_link = $current_item; $curr = FALSE; } while ($curr) { $link = $curr['link']; if ($link['in_active_trail']) { // Add the link to the trail, unless it links to its parent. if (!($link['type'] & MENU_LINKS_TO_PARENT)) { // The menu tree for the active trail may contain additional links // that have not been translated yet, since they contain dynamic // argument placeholders (%). Such links are not contained in regular // menu trees, and have only been loaded for the additional // translation that happens here, so as to be able to display them in // the breadcumb for the current page. // @see _menu_tree_check_access() // @see _menu_link_translate() if (strpos($link['href'], '%') !== FALSE) { _menu_link_translate($link, TRUE); } if ($link['access']) { $trail[] = $link; } } $tree = $curr['below'] ? $curr['below'] : array(); } list($key, $curr) = each($tree); } // Make sure the current page is in the trail to build the page title, by // appending either the preferred link or the menu router item for the // current page. Exclude it if we are on the front page. $last = end($trail); if ($preferred_link && $last['href'] != $preferred_link['href'] && !drupal_is_front_page()) { $trail[] = $preferred_link; } } return $trail; } /** * Looks up the preferred menu link for a given system path. * * @param $path * The path; for example, 'node/5'. The function will find the corresponding * menu link ('node/5' if it exists, or fallback to 'node/%'). * @param $selected_menu * The name of a menu used to restrict the search for a preferred menu link. * If not specified, all the menus returned by menu_get_active_menu_names() * will be used. * * @return * A fully translated menu link, or FALSE if no matching menu link was * found. The most specific menu link ('node/5' preferred over 'node/%') in * the most preferred menu (as defined by menu_get_active_menu_names()) is * returned. */ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { $preferred_links = &drupal_static(__FUNCTION__); if (!isset($path)) { $path = $_GET['q']; } if (empty($selected_menu)) { // Use an illegal menu name as the key for the preferred menu link. $selected_menu = MENU_PREFERRED_LINK; } if (!isset($preferred_links[$path])) { // Look for the correct menu link by building a list of candidate paths, // which are ordered by priority (translated hrefs are preferred over // untranslated paths). Afterwards, the most relevant path is picked from // the menus, ordered by menu preference. $item = menu_get_item($path); $path_candidates = array(); // 1. The current item href. $path_candidates[$item['href']] = $item['href']; // 2. The tab root href of the current item (if any). if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) { $path_candidates[$tab_root['href']] = $tab_root['href']; } // 3. The current item path (with wildcards). $path_candidates[$item['path']] = $item['path']; // 4. The tab root path of the current item (if any). if (!empty($tab_root)) { $path_candidates[$tab_root['path']] = $tab_root['path']; } // Retrieve a list of menu names, ordered by preference. $menu_names = menu_get_active_menu_names(); // Put the selected menu at the front of the list. array_unshift($menu_names, $selected_menu); $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); $query->fields('ml'); // Weight must be taken from {menu_links}, not {menu_router}. $query->addField('ml', 'weight', 'link_weight'); $query->fields('m'); $query->condition('ml.link_path', $path_candidates, 'IN'); // Sort candidates by link path and menu name. $candidates = array(); foreach ($query->execute() as $candidate) { $candidate['weight'] = $candidate['link_weight']; $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; // Add any menus not already in the menu name search list. if (!in_array($candidate['menu_name'], $menu_names)) { $menu_names[] = $candidate['menu_name']; } } // Store the most specific link for each menu. Also save the most specific // link of the most preferred menu in $preferred_link. foreach ($path_candidates as $link_path) { if (isset($candidates[$link_path])) { foreach ($menu_names as $menu_name) { if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) { $candidate_item = $candidates[$link_path][$menu_name]; $map = explode('/', $path); _menu_translate($candidate_item, $map); if ($candidate_item['access']) { $preferred_links[$path][$menu_name] = $candidate_item; if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) { // Store the most specific link. $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item; } } } } } } } return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE; } /** * Gets the active trail (path to root menu root) of the current page. * * If a trail is supplied to menu_set_active_trail(), that value is returned. If * a trail is not supplied to menu_set_active_trail(), the path to the current * page is calculated and returned. The calculated trail is also saved as a * static value for use by subsequent calls to menu_get_active_trail(). * * @return * Path to menu root of the current page, as an array of menu link items, * starting with the site's home page. Each link item is an associative array * with the following components: * - title: Title of the item. * - href: Drupal path of the item. * - localized_options: Options for passing into the l() function. * - type: A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to * indicate it's not really in the menu (used for the home page item). */ function menu_get_active_trail() { return menu_set_active_trail(); } /** * Gets the breadcrumb for the current page, as determined by the active trail. * * @see menu_set_active_trail() */ function menu_get_active_breadcrumb() { $breadcrumb = array(); // No breadcrumb for the front page. if (drupal_is_front_page()) { return $breadcrumb; } $item = menu_get_item(); if (!empty($item['access'])) { $active_trail = menu_get_active_trail(); // Allow modules to alter the breadcrumb, if possible, as that is much // faster than rebuilding an entirely new active trail. drupal_alter('menu_breadcrumb', $active_trail, $item); // Don't show a link to the current page in the breadcrumb trail. $end = end($active_trail); if ($item['href'] == $end['href']) { array_pop($active_trail); } // Remove the tab root (parent) if the current path links to its parent. // Normally, the tab root link is included in the breadcrumb, as soon as we // are on a local task or any other child link. However, if we are on a // default local task (e.g., node/%/view), then we do not want the tab root // link (e.g., node/%) to appear, as it would be identical to the current // page. Since this behavior also needs to work recursively (i.e., on // default local tasks of default local tasks), and since the last non-task // link in the trail is used as page title (see menu_get_active_title()), // this condition cannot be cleanly integrated into menu_get_active_trail(). // menu_get_active_trail() already skips all links that link to their parent // (commonly MENU_DEFAULT_LOCAL_TASK). In order to also hide the parent link // itself, we always remove the last link in the trail, if the current // router item links to its parent. if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) { array_pop($active_trail); } foreach ($active_trail as $parent) { $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']); } } return $breadcrumb; } /** * Gets the title of the current page, as determined by the active trail. */ function menu_get_active_title() { $active_trail = menu_get_active_trail(); foreach (array_reverse($active_trail) as $item) { if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) { return $item['title']; } } } /** * Gets a translated, access-checked menu link that is ready for rendering. * * This function should never be called from within node_load() or any other * function used as a menu object load function since an infinite recursion may * occur. * * @param $mlid * The mlid of the menu item. * * @return * A menu link, with $item['access'] filled and link translated for * rendering. */ function menu_link_load($mlid) { if (is_numeric($mlid)) { $query = db_select('menu_links', 'ml'); $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); $query->fields('ml'); // Weight should be taken from {menu_links}, not {menu_router}. $query->addField('ml', 'weight', 'link_weight'); $query->fields('m'); $query->condition('ml.mlid', $mlid); if ($item = $query->execute()->fetchAssoc()) { $item['weight'] = $item['link_weight']; _menu_link_translate($item); return $item; } } return FALSE; } /** * Clears the cached cached data for a single named menu. */ function menu_cache_clear($menu_name = 'navigation') { $cache_cleared = &drupal_static(__FUNCTION__, array()); if (empty($cache_cleared[$menu_name])) { cache_clear_all('links:' . $menu_name . ':', 'cache_menu', TRUE); $cache_cleared[$menu_name] = 1; } elseif ($cache_cleared[$menu_name] == 1) { drupal_register_shutdown_function('cache_clear_all', 'links:' . $menu_name . ':', 'cache_menu', TRUE); $cache_cleared[$menu_name] = 2; } // Also clear the menu system static caches. menu_reset_static_cache(); } /** * Clears all cached menu data. * * This should be called any time broad changes * might have been made to the router items or menu links. */ function menu_cache_clear_all() { cache_clear_all('*', 'cache_menu', TRUE); menu_reset_static_cache(); } /** * Resets the menu system static cache. */ function menu_reset_static_cache() { drupal_static_reset('_menu_build_tree'); drupal_static_reset('menu_tree'); drupal_static_reset('menu_tree_all_data'); drupal_static_reset('menu_tree_page_data'); drupal_static_reset('menu_load_all'); drupal_static_reset('menu_link_get_preferred'); } /** * Populates the database tables used by various menu functions. * * This function will clear and populate the {menu_router} table, add entries * to {menu_links} for new router items, and then remove stale items from * {menu_links}. If called from update.php or install.php, it will also * schedule a call to itself on the first real page load from * menu_execute_active_handler(), because the maintenance page environment * is different and leaves stale data in the menu tables. * * @return * TRUE if the menu was rebuilt, FALSE if another thread was rebuilding * in parallel and the current thread just waited for completion. */ function menu_rebuild() { if (!lock_acquire('menu_rebuild')) { // Wait for another request that is already doing this work. // We choose to block here since otherwise the router item may not // be available in menu_execute_active_handler() resulting in a 404. lock_wait('menu_rebuild'); return FALSE; } $transaction = db_transaction(); try { list($menu, $masks) = menu_router_build(); _menu_router_save($menu, $masks); _menu_navigation_links_rebuild($menu); // Clear the menu, page and block caches. menu_cache_clear_all(); _menu_clear_page_cache(); if (defined('MAINTENANCE_MODE')) { variable_set('menu_rebuild_needed', TRUE); } else { variable_del('menu_rebuild_needed'); } } catch (Exception $e) { $transaction->rollback(); watchdog_exception('menu', $e); } lock_release('menu_rebuild'); return TRUE; } /** * Collects and alters the menu definitions. */ function menu_router_build() { // We need to manually call each module so that we can know which module // a given item came from. $callbacks = array(); foreach (module_implements('menu') as $module) { $router_items = call_user_func($module . '_menu'); if (isset($router_items) && is_array($router_items)) { foreach (array_keys($router_items) as $path) { $router_items[$path]['module'] = $module; } $callbacks = array_merge($callbacks, $router_items); } } // Alter the menu as defined in modules, keys are like user/%user. drupal_alter('menu', $callbacks); list($menu, $masks) = _menu_router_build($callbacks); _menu_router_cache($menu); return array($menu, $masks); } /** * Stores the menu router if we have it in memory. */ function _menu_router_cache($new_menu = NULL) { $menu = &drupal_static(__FUNCTION__); if (isset($new_menu)) { $menu = $new_menu; } return $menu; } /** * Gets the menu router. */ function menu_get_router() { // Check first if we have it in memory already. $menu = _menu_router_cache(); if (empty($menu)) { list($menu, $masks) = menu_router_build(); } return $menu; } /** * Builds a link from a router item. */ function _menu_link_build($item) { // Suggested items are disabled by default. if ($item['type'] == MENU_SUGGESTED_ITEM) { $item['hidden'] = 1; } // Hide all items that are not visible in the tree. elseif (!($item['type'] & MENU_VISIBLE_IN_TREE)) { $item['hidden'] = -1; } // Note, we set this as 'system', so that we can be sure to distinguish all // the menu links generated automatically from entries in {menu_router}. $item['module'] = 'system'; $item += array( 'menu_name' => 'navigation', 'link_title' => $item['title'], 'link_path' => $item['path'], 'hidden' => 0, 'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])), ); return $item; } /** * Builds menu links for the items in the menu router. */ function _menu_navigation_links_rebuild($menu) { // Add normal and suggested items as links. $menu_links = array(); foreach ($menu as $path => $item) { if ($item['_visible']) { $menu_links[$path] = $item; $sort[$path] = $item['_number_parts']; } } if ($menu_links) { // Keep an array of processed menu links, to allow menu_link_save() to // check this for parents instead of querying the database. $parent_candidates = array(); // Make sure no child comes before its parent. array_multisort($sort, SORT_NUMERIC, $menu_links); foreach ($menu_links as $key => $item) { $existing_item = db_select('menu_links') ->fields('menu_links') ->condition('link_path', $item['path']) ->condition('module', 'system') ->execute()->fetchAssoc(); if ($existing_item) { $item['mlid'] = $existing_item['mlid']; // A change in hook_menu may move the link to a different menu if (empty($item['menu_name']) || ($item['menu_name'] == $existing_item['menu_name'])) { $item['menu_name'] = $existing_item['menu_name']; $item['plid'] = $existing_item['plid']; } else { // It moved to a new menu. Let menu_link_save() try to find a new // parent based on the path. unset($item['plid']); } $item['has_children'] = $existing_item['has_children']; $item['updated'] = $existing_item['updated']; } if ($existing_item && $existing_item['customized']) { $parent_candidates[$existing_item['mlid']] = $existing_item; } else { $item = _menu_link_build($item); menu_link_save($item, $existing_item, $parent_candidates); $parent_candidates[$item['mlid']] = $item; unset($menu_links[$key]); } } } $paths = array_keys($menu); // Updated and customized items whose router paths are gone need new ones. $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('menu_links', array( 'link_path', 'mlid', 'router_path', 'updated', )) ->condition(db_or() ->condition('updated', 1) ->condition(db_and() ->condition('router_path', $paths, 'NOT IN') ->condition('external', 0) ->condition('customized', 1) ) ) ->execute(); foreach ($result as $item) { $router_path = _menu_find_router_path($item['link_path']); if (!empty($router_path) && ($router_path != $item['router_path'] || $item['updated'])) { // If the router path and the link path matches, it's surely a working // item, so we clear the updated flag. $updated = $item['updated'] && $router_path != $item['link_path']; db_update('menu_links') ->fields(array( 'router_path' => $router_path, 'updated' => (int) $updated, )) ->condition('mlid', $item['mlid']) ->execute(); } } // Find any item whose router path does not exist any more. $result = db_select('menu_links') ->fields('menu_links') ->condition('router_path', $paths, 'NOT IN') ->condition('external', 0) ->condition('updated', 0) ->condition('customized', 0) ->orderBy('depth', 'DESC') ->execute(); // Remove all such items. Starting from those with the greatest depth will // minimize the amount of re-parenting done by menu_link_delete(). foreach ($result as $item) { _menu_delete_item($item, TRUE); } } /** * Clones an array of menu links. * * @param $links * An array of menu links to clone. * @param $menu_name * (optional) The name of a menu that the links will be cloned for. If not * set, the cloned links will be in the same menu as the original set of * links that were passed in. * * @return * An array of menu links with the same properties as the passed-in array, * but with the link identifiers removed so that a new link will be created * when any of them is passed in to menu_link_save(). * * @see menu_link_save() */ function menu_links_clone($links, $menu_name = NULL) { foreach ($links as &$link) { unset($link['mlid']); unset($link['plid']); if (isset($menu_name)) { $link['menu_name'] = $menu_name; } } return $links; } /** * Returns an array containing all links for a menu. * * @param $menu_name * The name of the menu whose links should be returned. * * @return * An array of menu links. */ function menu_load_links($menu_name) { $links = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)) ->fields('ml') ->condition('ml.menu_name', $menu_name) // Order by weight so as to be helpful for menus that are only one level // deep. ->orderBy('weight') ->execute() ->fetchAll(); foreach ($links as &$link) { $link['options'] = unserialize($link['options']); } return $links; } /** * Deletes all links for a menu. * * @param $menu_name * The name of the menu whose links will be deleted. */ function menu_delete_links($menu_name) { $links = menu_load_links($menu_name); foreach ($links as $link) { // To speed up the deletion process, we reset some link properties that // would trigger re-parenting logic in _menu_delete_item() and // _menu_update_parental_status(). $link['has_children'] = FALSE; $link['plid'] = 0; _menu_delete_item($link); } } /** * Delete one or several menu links. * * @param $mlid * A valid menu link mlid or NULL. If NULL, $path is used. * @param $path * The path to the menu items to be deleted. $mlid must be NULL. */ function menu_link_delete($mlid, $path = NULL) { if (isset($mlid)) { _menu_delete_item(db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc()); } else { $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $path)); foreach ($result as $link) { _menu_delete_item($link); } } } /** * Deletes a single menu link. * * @param $item * Item to be deleted. * @param $force * Forces deletion. Internal use only, setting to TRUE is discouraged. * * @see menu_link_delete() */ function _menu_delete_item($item, $force = FALSE) { $item = is_object($item) ? get_object_vars($item) : $item; if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) { // Children get re-attached to the item's parent. if ($item['has_children']) { $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = :plid", array(':plid' => $item['mlid'])); foreach ($result as $m) { $child = menu_link_load($m->mlid); $child['plid'] = $item['plid']; menu_link_save($child); } } // Notify modules we are deleting the item. module_invoke_all('menu_link_delete', $item); db_delete('menu_links')->condition('mlid', $item['mlid'])->execute(); // Update the has_children status of the parent. _menu_update_parental_status($item); menu_cache_clear($item['menu_name']); _menu_clear_page_cache(); } } /** * Saves a menu link. * * After calling this function, rebuild the menu cache using * menu_cache_clear_all(). * * @param $item * An associative array representing a menu link item, with elements: * - link_path: (required) The path of the menu item, which should be * normalized first by calling drupal_get_normal_path() on it. * - link_title: (required) Title to appear in menu for the link. * - menu_name: (optional) The machine name of the menu for the link. * Defaults to 'navigation'. * - weight: (optional) Integer to determine position in menu. Default is 0. * - expanded: (optional) Boolean that determines if the item is expanded. * - options: (optional) An array of options, see l() for more. * - mlid: (optional) Menu link identifier, the primary integer key for each * menu link. Can be set to an existing value, or to 0 or NULL * to insert a new link. * - plid: (optional) The mlid of the parent. * - router_path: (optional) The path of the relevant router item. * @param $existing_item * Optional, the current record from the {menu_links} table as an array. * @param $parent_candidates * Optional array of menu links keyed by mlid. Used by * _menu_navigation_links_rebuild() only. * * @return * The mlid of the saved menu link, or FALSE if the menu link could not be * saved. */ function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) { drupal_alter('menu_link', $item); // This is the easiest way to handle the unique internal path '', // since a path marked as external does not need to match a router path. $item['external'] = (url_is_external($item['link_path']) || $item['link_path'] == '') ? 1 : 0; // Load defaults. $item += array( 'menu_name' => 'navigation', 'weight' => 0, 'link_title' => '', 'hidden' => 0, 'has_children' => 0, 'expanded' => 0, 'options' => array(), 'module' => 'menu', 'customized' => 0, 'updated' => 0, ); if (isset($item['mlid'])) { if (!$existing_item) { $existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc(); } if ($existing_item) { $existing_item['options'] = unserialize($existing_item['options']); } } else { $existing_item = FALSE; } // Try to find a parent link. If found, assign it and derive its menu. $parent = _menu_link_find_parent($item, $parent_candidates); if (!empty($parent['mlid'])) { $item['plid'] = $parent['mlid']; $item['menu_name'] = $parent['menu_name']; } // If no corresponding parent link was found, move the link to the top-level. else { $item['plid'] = 0; } $menu_name = $item['menu_name']; if (!$existing_item) { $item['mlid'] = db_insert('menu_links') ->fields(array( 'menu_name' => $item['menu_name'], 'plid' => $item['plid'], 'link_path' => $item['link_path'], 'hidden' => $item['hidden'], 'external' => $item['external'], 'has_children' => $item['has_children'], 'expanded' => $item['expanded'], 'weight' => $item['weight'], 'module' => $item['module'], 'link_title' => $item['link_title'], 'options' => serialize($item['options']), 'customized' => $item['customized'], 'updated' => $item['updated'], )) ->execute(); } // Directly fill parents for top-level links. if ($item['plid'] == 0) { $item['p1'] = $item['mlid']; for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { $item["p$i"] = 0; } $item['depth'] = 1; } // Otherwise, ensure that this link's depth is not beyond the maximum depth // and fill parents based on the parent link. else { if ($item['has_children'] && $existing_item) { $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1; } else { $limit = MENU_MAX_DEPTH - 1; } if ($parent['depth'] > $limit) { return FALSE; } $item['depth'] = $parent['depth'] + 1; _menu_link_parents_set($item, $parent); } // Need to check both plid and menu_name, since plid can be 0 in any menu. if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) { _menu_link_move_children($item, $existing_item); } // Find the router_path. if (empty($item['router_path']) || !$existing_item || ($existing_item['link_path'] != $item['link_path'])) { if ($item['external']) { $item['router_path'] = ''; } else { // Find the router path which will serve this path. $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS); $item['router_path'] = _menu_find_router_path($item['link_path']); } } // If every value in $existing_item is the same in the $item, there is no // reason to run the update queries or clear the caches. We use // array_intersect_key() with the $item as the first parameter because // $item may have additional keys left over from building a router entry. // The intersect removes the extra keys, allowing a meaningful comparison. if (!$existing_item || (array_intersect_key($item, $existing_item) != $existing_item)) { db_update('menu_links') ->fields(array( 'menu_name' => $item['menu_name'], 'plid' => $item['plid'], 'link_path' => $item['link_path'], 'router_path' => $item['router_path'], 'hidden' => $item['hidden'], 'external' => $item['external'], 'has_children' => $item['has_children'], 'expanded' => $item['expanded'], 'weight' => $item['weight'], 'depth' => $item['depth'], 'p1' => $item['p1'], 'p2' => $item['p2'], 'p3' => $item['p3'], 'p4' => $item['p4'], 'p5' => $item['p5'], 'p6' => $item['p6'], 'p7' => $item['p7'], 'p8' => $item['p8'], 'p9' => $item['p9'], 'module' => $item['module'], 'link_title' => $item['link_title'], 'options' => serialize($item['options']), 'customized' => $item['customized'], )) ->condition('mlid', $item['mlid']) ->execute(); // Check the has_children status of the parent. _menu_update_parental_status($item); menu_cache_clear($menu_name); if ($existing_item && $menu_name != $existing_item['menu_name']) { menu_cache_clear($existing_item['menu_name']); } // Notify modules we have acted on a menu item. $hook = 'menu_link_insert'; if ($existing_item) { $hook = 'menu_link_update'; } module_invoke_all($hook, $item); // Now clear the cache. _menu_clear_page_cache(); } return $item['mlid']; } /** * Finds a possible parent for a given menu link. * * Because the parent of a given link might not exist anymore in the database, * we apply a set of heuristics to determine a proper parent: * * - use the passed parent link if specified and existing. * - else, use the first existing link down the previous link hierarchy * - else, for system menu links (derived from hook_menu()), reparent * based on the path hierarchy. * * @param $menu_link * A menu link. * @param $parent_candidates * An array of menu links keyed by mlid. * * @return * A menu link structure of the possible parent or FALSE if no valid parent * has been found. */ function _menu_link_find_parent($menu_link, $parent_candidates = array()) { $parent = FALSE; // This item is explicitely top-level, skip the rest of the parenting. if (isset($menu_link['plid']) && empty($menu_link['plid'])) { return $parent; } // If we have a parent link ID, try to use that. $candidates = array(); if (isset($menu_link['plid'])) { $candidates[] = $menu_link['plid']; } // Else, if we have a link hierarchy try to find a valid parent in there. if (!empty($menu_link['depth']) && $menu_link['depth'] > 1) { for ($depth = $menu_link['depth'] - 1; $depth >= 1; $depth--) { $candidates[] = $menu_link['p' . $depth]; } } foreach ($candidates as $mlid) { if (isset($parent_candidates[$mlid])) { $parent = $parent_candidates[$mlid]; } else { $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc(); } if ($parent) { return $parent; } } // If everything else failed, try to derive the parent from the path // hierarchy. This only makes sense for links derived from menu router // items (ie. from hook_menu()). if ($menu_link['module'] == 'system') { $query = db_select('menu_links'); $query->condition('module', 'system'); // We always respect the link's 'menu_name'; inheritance for router items is // ensured in _menu_router_build(). $query->condition('menu_name', $menu_link['menu_name']); // Find the parent - it must be unique. $parent_path = $menu_link['link_path']; do { $parent = FALSE; $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); $new_query = clone $query; $new_query->condition('link_path', $parent_path); // Only valid if we get a unique result. if ($new_query->countQuery()->execute()->fetchField() == 1) { $parent = $new_query->fields('menu_links')->execute()->fetchAssoc(); } } while ($parent === FALSE && $parent_path); } return $parent; } /** * Clears the page and block caches at most twice per page load. */ function _menu_clear_page_cache() { $cache_cleared = &drupal_static(__FUNCTION__, 0); // Clear the page and block caches, but at most twice, including at // the end of the page load when there are multiple links saved or deleted. if ($cache_cleared == 0) { cache_clear_all(); // Keep track of which menus have expanded items. _menu_set_expanded_menus(); $cache_cleared = 1; } elseif ($cache_cleared == 1) { drupal_register_shutdown_function('cache_clear_all'); // Keep track of which menus have expanded items. drupal_register_shutdown_function('_menu_set_expanded_menus'); $cache_cleared = 2; } } /** * Updates a list of menus with expanded items. */ function _menu_set_expanded_menus() { $names = db_query("SELECT menu_name FROM {menu_links} WHERE expanded <> 0 GROUP BY menu_name")->fetchCol(); variable_set('menu_expanded', $names); } /** * Finds the router path which will serve this path. * * @param $link_path * The path for we are looking up its router path. * * @return * A path from $menu keys or empty if $link_path points to a nonexisting * place. */ function _menu_find_router_path($link_path) { // $menu will only have data during a menu rebuild. $menu = _menu_router_cache(); $router_path = $link_path; $parts = explode('/', $link_path, MENU_MAX_PARTS); $ancestors = menu_get_ancestors($parts); if (empty($menu)) { // Not during a menu rebuild, so look up in the database. $router_path = (string) db_select('menu_router') ->fields('menu_router', array('path')) ->condition('path', $ancestors, 'IN') ->orderBy('fit', 'DESC') ->range(0, 1) ->execute()->fetchField(); } elseif (!isset($menu[$router_path])) { // Add an empty router path as a fallback. $ancestors[] = ''; foreach ($ancestors as $key => $router_path) { if (isset($menu[$router_path])) { // Exit the loop leaving $router_path as the first match. break; } } // If we did not find the path, $router_path will be the empty string // at the end of $ancestors. } return $router_path; } /** * Inserts, updates, or deletes an uncustomized menu link related to a module. * * @param $module * The name of the module. * @param $op * Operation to perform: insert, update or delete. * @param $link_path * The path this link points to. * @param $link_title * Title of the link to insert or new title to update the link to. * Unused for delete. * * @return * The insert op returns the mlid of the new item. Others op return NULL. */ function menu_link_maintain($module, $op, $link_path, $link_title) { switch ($op) { case 'insert': $menu_link = array( 'link_title' => $link_title, 'link_path' => $link_path, 'module' => $module, ); return menu_link_save($menu_link); break; case 'update': $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path AND module = :module AND customized = 0", array(':link_path' => $link_path, ':module' => $module))->fetchAll(PDO::FETCH_ASSOC); foreach ($result as $link) { $link['link_title'] = $link_title; $link['options'] = unserialize($link['options']); menu_link_save($link); } break; case 'delete': menu_link_delete(NULL, $link_path); break; } } /** * Finds the depth of an item's children relative to its depth. * * For example, if the item has a depth of 2, and the maximum of any child in * the menu link tree is 5, the relative depth is 3. * * @param $item * An array representing a menu link item. * * @return * The relative depth, or zero. * */ function menu_link_children_relative_depth($item) { $query = db_select('menu_links'); $query->addField('menu_links', 'depth'); $query->condition('menu_name', $item['menu_name']); $query->orderBy('depth', 'DESC'); $query->range(0, 1); $i = 1; $p = 'p1'; while ($i <= MENU_MAX_DEPTH && $item[$p]) { $query->condition($p, $item[$p]); $p = 'p' . ++$i; } $max_depth = $query->execute()->fetchField(); return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0; } /** * Updates the children of a menu link that is being moved. * * The menu name, parents (p1 - p6), and depth are updated for all children of * the link, and the has_children status of the previous parent is updated. */ function _menu_link_move_children($item, $existing_item) { $query = db_update('menu_links'); $query->fields(array('menu_name' => $item['menu_name'])); $p = 'p1'; $expressions = array(); for ($i = 1; $i <= $item['depth']; $p = 'p' . ++$i) { $expressions[] = array($p, ":p_$i", array(":p_$i" => $item[$p])); } $j = $existing_item['depth'] + 1; while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) { $expressions[] = array('p' . $i++, 'p' . $j++, array()); } while ($i <= MENU_MAX_DEPTH) { $expressions[] = array('p' . $i++, 0, array()); } $shift = $item['depth'] - $existing_item['depth']; if ($shift > 0) { // The order of expressions must be reversed so the new values don't // overwrite the old ones before they can be used because "Single-table // UPDATE assignments are generally evaluated from left to right" // see: http://dev.mysql.com/doc/refman/5.0/en/update.html $expressions = array_reverse($expressions); } foreach ($expressions as $expression) { $query->expression($expression[0], $expression[1], $expression[2]); } $query->expression('depth', 'depth + :depth', array(':depth' => $shift)); $query->condition('menu_name', $existing_item['menu_name']); $p = 'p1'; for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p' . ++$i) { $query->condition($p, $existing_item[$p]); } $query->execute(); // Check the has_children status of the parent, while excluding this item. _menu_update_parental_status($existing_item, TRUE); } /** * Checks and updates the 'has_children' status for the parent of a link. */ function _menu_update_parental_status($item, $exclude = FALSE) { // If plid == 0, there is nothing to update. if ($item['plid']) { // Check if at least one visible child exists in the table. $query = db_select('menu_links'); $query->addField('menu_links', 'mlid'); $query->condition('menu_name', $item['menu_name']); $query->condition('hidden', 0); $query->condition('plid', $item['plid']); $query->range(0, 1); if ($exclude) { $query->condition('mlid', $item['mlid'], '<>'); } $parent_has_children = ((bool) $query->execute()->fetchField()) ? 1 : 0; db_update('menu_links') ->fields(array('has_children' => $parent_has_children)) ->condition('mlid', $item['plid']) ->execute(); } } /** * Sets the p1 through p9 values for a menu link being saved. */ function _menu_link_parents_set(&$item, $parent) { $i = 1; while ($i < $item['depth']) { $p = 'p' . $i++; $item[$p] = $parent[$p]; } $p = 'p' . $i++; // The parent (p1 - p9) corresponding to the depth always equals the mlid. $item[$p] = $item['mlid']; while ($i <= MENU_MAX_DEPTH) { $p = 'p' . $i++; $item[$p] = 0; } } /** * Builds the router table based on the data from hook_menu(). */ function _menu_router_build($callbacks) { // First pass: separate callbacks from paths, making paths ready for // matching. Calculate fitness, and fill some default values. $menu = array(); $masks = array(); foreach ($callbacks as $path => $item) { $load_functions = array(); $to_arg_functions = array(); $fit = 0; $move = FALSE; $parts = explode('/', $path, MENU_MAX_PARTS); $number_parts = count($parts); // We store the highest index of parts here to save some work in the fit // calculation loop. $slashes = $number_parts - 1; // Extract load and to_arg functions. foreach ($parts as $k => $part) { $match = FALSE; // Look for wildcards in the form allowed to be used in PHP functions, // because we are using these to construct the load function names. if (preg_match('/^%(|' . DRUPAL_PHP_FUNCTION_PATTERN . ')$/', $part, $matches)) { if (empty($matches[1])) { $match = TRUE; $load_functions[$k] = NULL; } else { if (function_exists($matches[1] . '_to_arg')) { $to_arg_functions[$k] = $matches[1] . '_to_arg'; $load_functions[$k] = NULL; $match = TRUE; } if (function_exists($matches[1] . '_load')) { $function = $matches[1] . '_load'; // Create an array of arguments that will be passed to the _load // function when this menu path is checked, if 'load arguments' // exists. $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function; $match = TRUE; } } } if ($match) { $parts[$k] = '%'; } else { $fit |= 1 << ($slashes - $k); } } if ($fit) { $move = TRUE; } else { // If there is no %, it fits maximally. $fit = (1 << $number_parts) - 1; } $masks[$fit] = 1; $item['_load_functions'] = $load_functions; $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions); $item += array( 'title' => '', 'weight' => 0, 'type' => MENU_NORMAL_ITEM, 'module' => '', '_number_parts' => $number_parts, '_parts' => $parts, '_fit' => $fit, ); $item += array( '_visible' => (bool) ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB), '_tab' => (bool) ($item['type'] & MENU_IS_LOCAL_TASK), ); if ($move) { $new_path = implode('/', $item['_parts']); $menu[$new_path] = $item; $sort[$new_path] = $number_parts; } else { $menu[$path] = $item; $sort[$path] = $number_parts; } } array_multisort($sort, SORT_NUMERIC, $menu); // Apply inheritance rules. foreach ($menu as $path => $v) { $item = &$menu[$path]; if (!$item['_tab']) { // Non-tab items. $item['tab_parent'] = ''; $item['tab_root'] = $path; } // If not specified, assign the default tab type for local tasks. elseif (!isset($item['context'])) { $item['context'] = MENU_CONTEXT_PAGE; } for ($i = $item['_number_parts'] - 1; $i; $i--) { $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); if (isset($menu[$parent_path])) { $parent = &$menu[$parent_path]; // If we have no menu name, try to inherit it from parent items. if (!isset($item['menu_name'])) { // If the parent item of this item does not define a menu name (and no // previous iteration assigned one already), try to find the menu name // of the parent item in the currently stored menu links. if (!isset($parent['menu_name'])) { $menu_name = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path AND module = 'system'", array(':router_path' => $parent_path))->fetchField(); if ($menu_name) { $parent['menu_name'] = $menu_name; } } // If the parent item defines a menu name, inherit it. if (!empty($parent['menu_name'])) { $item['menu_name'] = $parent['menu_name']; } } if (!isset($item['tab_parent'])) { // Parent stores the parent of the path. $item['tab_parent'] = $parent_path; } if (!isset($item['tab_root']) && !$parent['_tab']) { $item['tab_root'] = $parent_path; } // If an access callback is not found for a default local task we use // the callback from the parent, since we expect them to be identical. // In all other cases, the access parameters must be specified. if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) { $item['access callback'] = $parent['access callback']; if (!isset($item['access arguments']) && isset($parent['access arguments'])) { $item['access arguments'] = $parent['access arguments']; } } // Same for page callbacks. if (!isset($item['page callback']) && isset($parent['page callback'])) { $item['page callback'] = $parent['page callback']; if (!isset($item['page arguments']) && isset($parent['page arguments'])) { $item['page arguments'] = $parent['page arguments']; } if (!isset($item['file path']) && isset($parent['file path'])) { $item['file path'] = $parent['file path']; } if (!isset($item['file']) && isset($parent['file'])) { $item['file'] = $parent['file']; if (empty($item['file path']) && isset($item['module']) && isset($parent['module']) && $item['module'] != $parent['module']) { $item['file path'] = drupal_get_path('module', $parent['module']); } } } // Same for delivery callbacks. if (!isset($item['delivery callback']) && isset($parent['delivery callback'])) { $item['delivery callback'] = $parent['delivery callback']; } // Same for theme callbacks. if (!isset($item['theme callback']) && isset($parent['theme callback'])) { $item['theme callback'] = $parent['theme callback']; if (!isset($item['theme arguments']) && isset($parent['theme arguments'])) { $item['theme arguments'] = $parent['theme arguments']; } } // Same for load arguments: if a loader doesn't have any explict // arguments, try to find arguments in the parent. if (!isset($item['load arguments'])) { foreach ($item['_load_functions'] as $k => $function) { // This loader doesn't have any explict arguments... if (!is_array($function)) { // ... check the parent for a loader at the same position // using the same function name and defining arguments... if (isset($parent['_load_functions'][$k]) && is_array($parent['_load_functions'][$k]) && key($parent['_load_functions'][$k]) === $function) { // ... and inherit the arguments on the child. $item['_load_functions'][$k] = $parent['_load_functions'][$k]; } } } } } } if (!isset($item['access callback']) && isset($item['access arguments'])) { // Default callback. $item['access callback'] = 'user_access'; } if (!isset($item['access callback']) || empty($item['page callback'])) { $item['access callback'] = 0; } if (is_bool($item['access callback'])) { $item['access callback'] = intval($item['access callback']); } $item['load_functions'] = empty($item['_load_functions']) ? '' : serialize($item['_load_functions']); $item += array( 'access arguments' => array(), 'access callback' => '', 'page arguments' => array(), 'page callback' => '', 'delivery callback' => '', 'title arguments' => array(), 'title callback' => 't', 'theme arguments' => array(), 'theme callback' => '', 'description' => '', 'position' => '', 'context' => 0, 'tab_parent' => '', 'tab_root' => $path, 'path' => $path, 'file' => '', 'file path' => '', 'include file' => '', ); // Calculate out the file to be included for each callback, if any. if ($item['file']) { $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']); $item['include file'] = $file_path . '/' . $item['file']; } } // Sort the masks so they are in order of descending fit. $masks = array_keys($masks); rsort($masks); return array($menu, $masks); } /** * Saves data from menu_router_build() to the router table. */ function _menu_router_save($menu, $masks) { // Delete the existing router since we have some data to replace it. db_truncate('menu_router')->execute(); // Prepare insert object. $insert = db_insert('menu_router') ->fields(array( 'path', 'load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'delivery_callback', 'fit', 'number_parts', 'context', 'tab_parent', 'tab_root', 'title', 'title_callback', 'title_arguments', 'theme_callback', 'theme_arguments', 'type', 'description', 'position', 'weight', 'include_file', )); $num_records = 0; foreach ($menu as $path => $item) { // Fill in insert object values. $insert->values(array( 'path' => $item['path'], 'load_functions' => $item['load_functions'], 'to_arg_functions' => $item['to_arg_functions'], 'access_callback' => $item['access callback'], 'access_arguments' => serialize($item['access arguments']), 'page_callback' => $item['page callback'], 'page_arguments' => serialize($item['page arguments']), 'delivery_callback' => $item['delivery callback'], 'fit' => $item['_fit'], 'number_parts' => $item['_number_parts'], 'context' => $item['context'], 'tab_parent' => $item['tab_parent'], 'tab_root' => $item['tab_root'], 'title' => $item['title'], 'title_callback' => $item['title callback'], 'title_arguments' => ($item['title arguments'] ? serialize($item['title arguments']) : ''), 'theme_callback' => $item['theme callback'], 'theme_arguments' => serialize($item['theme arguments']), 'type' => $item['type'], 'description' => $item['description'], 'position' => $item['position'], 'weight' => $item['weight'], 'include_file' => $item['include file'], )); // Execute in batches to avoid the memory overhead of all of those records // in the query object. if (++$num_records == 20) { $insert->execute(); $num_records = 0; } } // Insert any remaining records. $insert->execute(); // Store the masks. variable_set('menu_masks', $masks); return $menu; } /** * Checks whether the site is in maintenance mode. * * This function will log the current user out and redirect to front page * if the current user has no 'access site in maintenance mode' permission. * * @param $check_only * If this is set to TRUE, the function will perform the access checks and * return the site offline status, but not log the user out or display any * messages. * * @return * FALSE if the site is not in maintenance mode, the user login page is * displayed, or the user has the 'access site in maintenance mode' * permission. TRUE for anonymous users not being on the login page when the * site is in maintenance mode. */ function _menu_site_is_offline($check_only = FALSE) { // Check if site is in maintenance mode. if (variable_get('maintenance_mode', 0)) { if (user_access('access site in maintenance mode')) { // Ensure that the maintenance mode message is displayed only once // (allowing for page redirects) and specifically suppress its display on // the maintenance mode settings page. if (!$check_only && $_GET['q'] != 'admin/config/development/maintenance') { if (user_access('administer site configuration')) { drupal_set_message(t('Operating in maintenance mode. Go online.', array('@url' => url('admin/config/development/maintenance'))), 'status', FALSE); } else { drupal_set_message(t('Operating in maintenance mode.'), 'status', FALSE); } } } else { return TRUE; } } return FALSE; } /** * @} End of "defgroup menu". */ drupal-7.26/includes/batch.inc0000644001412200141220000004213112265562324015603 0ustar benderbender $id, ':token' => drupal_get_token($id), ))->fetchField(); if ($batch) { return unserialize($batch); } return FALSE; } /** * Renders the batch processing page based on the current state of the batch. * * @see _batch_shutdown() */ function _batch_page() { $batch = &batch_get(); if (!isset($_REQUEST['id'])) { return FALSE; } // Retrieve the current state of the batch. if (!$batch) { $batch = batch_load($_REQUEST['id']); if (!$batch) { drupal_set_message(t('No active batch.'), 'error'); drupal_goto(); } } // Register database update for the end of processing. drupal_register_shutdown_function('_batch_shutdown'); // Add batch-specific CSS. foreach ($batch['sets'] as $batch_set) { if (isset($batch_set['css'])) { foreach ($batch_set['css'] as $css) { drupal_add_css($css); } } } $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; $output = NULL; switch ($op) { case 'start': $output = _batch_start(); break; case 'do': // JavaScript-based progress page callback. _batch_do(); break; case 'do_nojs': // Non-JavaScript-based progress page. $output = _batch_progress_page_nojs(); break; case 'finished': $output = _batch_finished(); break; } return $output; } /** * Initializes the batch processing. * * JavaScript-enabled clients are identified by the 'has_js' cookie set in * drupal.js. If no JavaScript-enabled page has been visited during the current * user's browser session, the non-JavaScript version is returned. */ function _batch_start() { if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) { return _batch_progress_page_js(); } else { return _batch_progress_page_nojs(); } } /** * Outputs a batch processing page with JavaScript support. * * This initializes the batch and error messages. Note that in JavaScript-based * processing, the batch processing page is displayed only once and updated via * AHAH requests, so only the first batch set gets to define the page title. * Titles specified by subsequent batch sets are not displayed. * * @see batch_set() * @see _batch_do() */ function _batch_progress_page_js() { $batch = batch_get(); $current_set = _batch_current_set(); drupal_set_title($current_set['title'], PASS_THROUGH); // Merge required query parameters for batch processing into those provided by // batch_set() or hook_batch_alter(). $batch['url_options']['query']['id'] = $batch['id']; $js_setting = array( 'batch' => array( 'errorMessage' => $current_set['error_message'] . '
      ' . $batch['error_message'], 'initMessage' => $current_set['init_message'], 'uri' => url($batch['url'], $batch['url_options']), ), ); drupal_add_js($js_setting, 'setting'); drupal_add_library('system', 'drupal.batch'); return '
      '; } /** * Does one execution pass with JavaScript and returns progress to the browser. * * @see _batch_progress_page_js() * @see _batch_process() */ function _batch_do() { // HTTP POST required. if ($_SERVER['REQUEST_METHOD'] != 'POST') { drupal_set_message(t('HTTP POST is required.'), 'error'); drupal_set_title(t('Error')); return ''; } // Perform actual processing. list($percentage, $message) = _batch_process(); drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); } /** * Outputs a batch processing page without JavaScript support. * * @see _batch_process() */ function _batch_progress_page_nojs() { $batch = &batch_get(); $current_set = _batch_current_set(); drupal_set_title($current_set['title'], PASS_THROUGH); $new_op = 'do_nojs'; if (!isset($batch['running'])) { // This is the first page so we return some output immediately. $percentage = 0; $message = $current_set['init_message']; $batch['running'] = TRUE; } else { // This is one of the later requests; do some processing first. // Error handling: if PHP dies due to a fatal error (e.g. a nonexistent // function), it will output whatever is in the output buffer, followed by // the error message. ob_start(); $fallback = $current_set['error_message'] . '
      ' . $batch['error_message']; $fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => FALSE)); // We strip the end of the page using a marker in the template, so any // additional HTML output by PHP shows up inside the page rather than below // it. While this causes invalid HTML, the same would be true if we didn't, // as content is not allowed to appear after anyway. list($fallback) = explode('', $fallback); print $fallback; // Perform actual processing. list($percentage, $message) = _batch_process($batch); if ($percentage == 100) { $new_op = 'finished'; } // PHP did not die; remove the fallback output. ob_end_clean(); } // Merge required query parameters for batch processing into those provided by // batch_set() or hook_batch_alter(). $batch['url_options']['query']['id'] = $batch['id']; $batch['url_options']['query']['op'] = $new_op; $url = url($batch['url'], $batch['url_options']); $element = array( '#tag' => 'meta', '#attributes' => array( 'http-equiv' => 'Refresh', 'content' => '0; URL=' . $url, ), ); drupal_add_html_head($element, 'batch_progress_meta_refresh'); return theme('progress_bar', array('percent' => $percentage, 'message' => $message)); } /** * Processes sets in a batch. * * If the batch was marked for progressive execution (default), this executes as * many operations in batch sets until an execution time of 1 second has been * exceeded. It will continue with the next operation of the same batch set in * the next request. * * @return * An array containing a completion value (in percent) and a status message. */ function _batch_process() { $batch = &batch_get(); $current_set = &_batch_current_set(); // Indicate that this batch set needs to be initialized. $set_changed = TRUE; // If this batch was marked for progressive execution (e.g. forms submitted by // drupal_form_submit()), initialize a timer to determine whether we need to // proceed with the same batch phase when a processing time of 1 second has // been exceeded. if ($batch['progressive']) { timer_start('batch_processing'); } if (empty($current_set['start'])) { $current_set['start'] = microtime(TRUE); } $queue = _batch_queue($current_set); while (!$current_set['success']) { // If this is the first time we iterate this batch set in the current // request, we check if it requires an additional file for functions // definitions. if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { include_once DRUPAL_ROOT . '/' . $current_set['file']; } $task_message = ''; // Assume a single pass operation and set the completion level to 1 by // default. $finished = 1; if ($item = $queue->claimItem()) { list($function, $args) = $item->data; // Build the 'context' array and execute the function call. $batch_context = array( 'sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message, ); call_user_func_array($function, array_merge($args, array(&$batch_context))); if ($finished >= 1) { // Make sure this step is not counted twice when computing $current. $finished = 0; // Remove the processed operation and clear the sandbox. $queue->deleteItem($item); $current_set['count']--; $current_set['sandbox'] = array(); } } // When all operations in the current batch set are completed, browse // through the remaining sets, marking them 'successfully processed' // along the way, until we find a set that contains operations. // _batch_next_set() executes form submit handlers stored in 'control' // sets (see form_execute_handlers()), which can in turn add new sets to // the batch. $set_changed = FALSE; $old_set = $current_set; while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) { $current_set = &_batch_current_set(); $current_set['start'] = microtime(TRUE); $set_changed = TRUE; } // At this point, either $current_set contains operations that need to be // processed or all sets have been completed. $queue = _batch_queue($current_set); // If we are in progressive mode, break processing after 1 second. if ($batch['progressive'] && timer_read('batch_processing') > 1000) { // Record elapsed wall clock time. $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2); break; } } if ($batch['progressive']) { // Gather progress information. // Reporting 100% progress will cause the whole batch to be considered // processed. If processing was paused right after moving to a new set, // we have to use the info from the new (unprocessed) set. if ($set_changed && isset($current_set['queue'])) { // Processing will continue with a fresh batch set. $remaining = $current_set['count']; $total = $current_set['total']; $progress_message = $current_set['init_message']; $task_message = ''; } else { // Processing will continue with the current batch set. $remaining = $old_set['count']; $total = $old_set['total']; $progress_message = $old_set['progress_message']; } // Total progress is the number of operations that have fully run plus the // completion level of the current operation. $current = $total - $remaining + $finished; $percentage = _batch_api_percentage($total, $current); $elapsed = isset($current_set['elapsed']) ? $current_set['elapsed'] : 0; $values = array( '@remaining' => $remaining, '@total' => $total, '@current' => floor($current), '@percentage' => $percentage, '@elapsed' => format_interval($elapsed / 1000), // If possible, estimate remaining processing time. '@estimate' => ($current > 0) ? format_interval(($elapsed * ($total - $current) / $current) / 1000) : '-', ); $message = strtr($progress_message, $values); if (!empty($message)) { $message .= '
      '; } if (!empty($task_message)) { $message .= $task_message; } return array($percentage, $message); } else { // If we are not in progressive mode, the entire batch has been processed. return _batch_finished(); } } /** * Formats the percent completion for a batch set. * * @param $total * The total number of operations. * @param $current * The number of the current operation. This may be a floating point number * rather than an integer in the case of a multi-step operation that is not * yet complete; in that case, the fractional part of $current represents the * fraction of the operation that has been completed. * * @return * The properly formatted percentage, as a string. We output percentages * using the correct number of decimal places so that we never print "100%" * until we are finished, but we also never print more decimal places than * are meaningful. * * @see _batch_process() */ function _batch_api_percentage($total, $current) { if (!$total || $total == $current) { // If $total doesn't evaluate as true or is equal to the current set, then // we're finished, and we can return "100". $percentage = "100"; } else { // We add a new digit at 200, 2000, etc. (since, for example, 199/200 // would round up to 100% if we didn't). $decimal_places = max(0, floor(log10($total / 2.0)) - 1); do { // Calculate the percentage to the specified number of decimal places. $percentage = sprintf('%01.' . $decimal_places . 'f', round($current / $total * 100, $decimal_places)); // When $current is an integer, the above calculation will always be // correct. However, if $current is a floating point number (in the case // of a multi-step batch operation that is not yet complete), $percentage // may be erroneously rounded up to 100%. To prevent that, we add one // more decimal place and try again. $decimal_places++; } while ($percentage == '100'); } return $percentage; } /** * Returns the batch set being currently processed. */ function &_batch_current_set() { $batch = &batch_get(); return $batch['sets'][$batch['current_set']]; } /** * Retrieves the next set in a batch. * * If there is a subsequent set in this batch, assign it as the new set to * process and execute its form submit handler (if defined), which may add * further sets to this batch. * * @return * TRUE if a subsequent set was found in the batch. */ function _batch_next_set() { $batch = &batch_get(); if (isset($batch['sets'][$batch['current_set'] + 1])) { $batch['current_set']++; $current_set = &_batch_current_set(); if (isset($current_set['form_submit']) && ($function = $current_set['form_submit']) && function_exists($function)) { // We use our stored copies of $form and $form_state to account for // possible alterations by previous form submit handlers. $function($batch['form_state']['complete form'], $batch['form_state']); } return TRUE; } } /** * Ends the batch processing. * * Call the 'finished' callback of each batch set to allow custom handling of * the results and resolve page redirection. */ function _batch_finished() { $batch = &batch_get(); // Execute the 'finished' callbacks for each batch set, if defined. foreach ($batch['sets'] as $batch_set) { if (isset($batch_set['finished'])) { // Check if the set requires an additional file for function definitions. if (isset($batch_set['file']) && is_file($batch_set['file'])) { include_once DRUPAL_ROOT . '/' . $batch_set['file']; } if (function_exists($batch_set['finished'])) { $queue = _batch_queue($batch_set); $operations = $queue->getAllItems(); $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000)); } } } // Clean up the batch table and unset the static $batch variable. if ($batch['progressive']) { db_delete('batch') ->condition('bid', $batch['id']) ->execute(); foreach ($batch['sets'] as $batch_set) { if ($queue = _batch_queue($batch_set)) { $queue->deleteQueue(); } } } $_batch = $batch; $batch = NULL; // Clean-up the session. Not needed for CLI updates. if (isset($_SESSION)) { unset($_SESSION['batches'][$batch['id']]); if (empty($_SESSION['batches'])) { unset($_SESSION['batches']); } } // Redirect if needed. if ($_batch['progressive']) { // Revert the 'destination' that was saved in batch_process(). if (isset($_batch['destination'])) { $_GET['destination'] = $_batch['destination']; } // Determine the target path to redirect to. if (!isset($_batch['form_state']['redirect'])) { if (isset($_batch['redirect'])) { $_batch['form_state']['redirect'] = $_batch['redirect']; } else { $_batch['form_state']['redirect'] = $_batch['source_url']; } } // Use drupal_redirect_form() to handle the redirection logic. drupal_redirect_form($_batch['form_state']); // If no redirection happened, redirect to the originating page. In case the // form needs to be rebuilt, save the final $form_state for // drupal_build_form(). if (!empty($_batch['form_state']['rebuild'])) { $_SESSION['batch_form_state'] = $_batch['form_state']; } $function = $_batch['redirect_callback']; if (function_exists($function)) { $function($_batch['source_url'], array('query' => array('op' => 'finish', 'id' => $_batch['id']))); } } } /** * Shutdown function: Stores the current batch data for the next request. * * @see _batch_page() * @see drupal_register_shutdown_function() */ function _batch_shutdown() { if ($batch = batch_get()) { db_update('batch') ->fields(array('batch' => serialize($batch))) ->condition('bid', $batch['id']) ->execute(); } } drupal-7.26/includes/json-encode.inc0000644001412200141220000000616412265562324016734 0ustar benderbender '\u005C', '"' => '\u0022', "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002', "\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005', "\x06" => '\u0006', "\x07" => '\u0007', "\x08" => '\u0008', "\x09" => '\u0009', "\x0a" => '\u000A', "\x0b" => '\u000B', "\x0c" => '\u000C', "\x0d" => '\u000D', "\x0e" => '\u000E', "\x0f" => '\u000F', "\x10" => '\u0010', "\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013', "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016', "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019', "\x1a" => '\u001A', "\x1b" => '\u001B', "\x1c" => '\u001C', "\x1d" => '\u001D', "\x1e" => '\u001E', "\x1f" => '\u001F', // Prevent browsers from interpreting these as as special. "'" => '\u0027', '<' => '\u003C', '>' => '\u003E', '&' => '\u0026', // Prevent browsers from interpreting the solidus as special and // non-compliant JSON parsers from interpreting // as a comment. '/' => '\u002F', // While these are allowed unescaped according to ECMA-262, section // 15.12.2, they cause problems in some JSON parsers. "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator. "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator. ); return '"' . strtr($var, $replace_pairs) . '"'; case 'array': // Arrays in JSON can't be associative. If the array is empty or if it // has sequential whole number keys starting with 0, it's not associative // so we can go ahead and convert it as an array. if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) { $output = array(); foreach ($var as $v) { $output[] = drupal_json_encode_helper($v); } return '[ ' . implode(', ', $output) . ' ]'; } // Otherwise, fall through to convert the array as an object. case 'object': $output = array(); foreach ($var as $k => $v) { $output[] = drupal_json_encode_helper(strval($k)) . ':' . drupal_json_encode_helper($v); } return '{' . implode(', ', $output) . '}'; default: return 'null'; } } drupal-7.26/includes/ajax.inc0000644001412200141220000013350112265562324015447 0ustar benderbender 'select', * '#options' => array( * 'one' => 'one', * 'two' => 'two', * 'three' => 'three', * ), * '#ajax' => array( * 'callback' => 'ajax_example_simplest_callback', * 'wrapper' => 'replace_textfield_div', * ), * ); * // This entire form element will be replaced with an updated value. * $form['replace_textfield'] = array( * '#type' => 'textfield', * '#title' => t("The default value will be changed"), * '#description' => t("Say something about why you chose") . "'" . * (!empty($form_state['values']['changethis']) * ? $form_state['values']['changethis'] : t("Not changed yet")) . "'", * '#prefix' => '
      ', * '#suffix' => '
      ', * ); * return $form; * } * * function ajax_example_simplest_callback($form, $form_state) { * // The form has already been submitted and updated. We can return the replaced * // item as it is. * return $form['replace_textfield']; * } * @endcode * * In the above example, the 'changethis' element is Ajax-enabled. The default * #ajax['event'] is 'change', so when the 'changethis' element changes, * an Ajax call is made. The form is submitted and reprocessed, and then the * callback is called. In this case, the form has been automatically * built changing $form['replace_textfield']['#description'], so the callback * just returns that part of the form. * * To implement Ajax handling in a form, add '#ajax' to the form * definition of a field. That field will trigger an Ajax event when it is * clicked (or changed, depending on the kind of field). #ajax supports * the following parameters (either 'path' or 'callback' is required at least): * - #ajax['callback']: The callback to invoke to handle the server side of the * Ajax event, which will receive a $form and $form_state as arguments, and * returns a renderable array (most often a form or form fragment), an HTML * string, or an array of Ajax commands. If returning a renderable array or * a string, the value will replace the original element named in * #ajax['wrapper'], and * theme_status_messages() * will be prepended to that * element. (If the status messages are not wanted, return an array * of Ajax commands instead.) * #ajax['wrapper']. If an array of Ajax commands is returned, it will be * executed by the calling code. * - #ajax['path']: The menu path to use for the request. This is often omitted * and the default is used. This path should map * to a menu page callback that returns data using ajax_render(). Defaults to * 'system/ajax', which invokes ajax_form_callback(), eventually calling * the function named in #ajax['callback']. If you use a custom * path, you must set up the menu entry and handle the entire callback in your * own code. * - #ajax['wrapper']: The CSS ID of the area to be replaced by the content * returned by the #ajax['callback'] function. The content returned from * the callback will replace the entire element named by #ajax['wrapper']. * The wrapper is usually created using #prefix and #suffix properties in the * form. Note that this is the wrapper ID, not a CSS selector. So to replace * the element referred to by the CSS selector #some-selector on the page, * use #ajax['wrapper'] = 'some-selector', not '#some-selector'. * - #ajax['effect']: The jQuery effect to use when placing the new HTML. * Defaults to no effect. Valid options are 'none', 'slide', or 'fade'. * - #ajax['speed']: The effect speed to use. Defaults to 'slow'. May be * 'slow', 'fast' or a number in milliseconds which represents the length * of time the effect should run. * - #ajax['event']: The JavaScript event to respond to. This is normally * selected automatically for the type of form widget being used, and * is only needed if you need to override the default behavior. * - #ajax['prevent']: A JavaScript event to prevent when 'event' is triggered. * Defaults to 'click' for #ajax on #type 'submit', 'button', and * 'image_button'. Multiple events may be specified separated by spaces. * For example, when binding #ajax behaviors to form buttons, pressing the * ENTER key within a textfield triggers the 'click' event of the form's first * submit button. Triggering Ajax in this situation leads to problems, like * breaking autocomplete textfields. Because of that, Ajax behaviors are bound * to the 'mousedown' event on form buttons by default. However, binding to * 'mousedown' rather than 'click' means that it is possible to trigger a * click by pressing the mouse, holding the mouse button down until the Ajax * request is complete and the button is re-enabled, and then releasing the * mouse button. For this case, 'prevent' can be set to 'click', so an * additional event handler is bound to prevent such a click from triggering a * non-Ajax form submission. This also prevents a textfield's ENTER press * triggering a button's non-Ajax form submission behavior. * - #ajax['method']: The jQuery method to use to place the new HTML. * Defaults to 'replaceWith'. May be: 'replaceWith', 'append', 'prepend', * 'before', 'after', or 'html'. See the * @link http://api.jquery.com/category/manipulation/ jQuery manipulators documentation @endlink * for more information on these methods. * - #ajax['progress']: Choose either a throbber or progress bar that is * displayed while awaiting a response from the callback, and add an optional * message. Possible keys: 'type', 'message', 'url', 'interval'. * More information is available in the * @link forms_api_reference.html Form API Reference @endlink * * In addition to using Form API for doing in-form modification, Ajax may be * enabled by adding classes to buttons and links. By adding the 'use-ajax' * class to a link, the link will be loaded via an Ajax call. When using this * method, the href of the link can contain '/nojs/' as part of the path. When * the Ajax framework makes the request, it will convert this to '/ajax/'. * The server is then able to easily tell if this request was made through an * actual Ajax request or in a degraded state, and respond appropriately. * * Similarly, submit buttons can be given the class 'use-ajax-submit'. The * form will then be submitted via Ajax to the path specified in the #action. * Like the ajax-submit class above, this path will have '/nojs/' replaced with * '/ajax/' so that the submit handler can tell if the form was submitted * in a degraded state or not. * * When responding to Ajax requests, the server should do what it needs to do * for that request, then create a commands array. This commands array will * be converted to a JSON object and returned to the client, which will then * iterate over the array and process it like a macro language. * * Each command item is an associative array which will be converted to a * command object on the JavaScript side. $command_item['command'] is the type * of command, e.g. 'alert' or 'replace', and will correspond to a method in the * Drupal.ajax[command] space. The command array may contain any other data that * the command needs to process, e.g. 'method', 'selector', 'settings', etc. * * Commands are usually created with a couple of helper functions, so they * look like this: * @code * $commands = array(); * // Replace the content of '#object-1' on the page with 'some html here'. * $commands[] = ajax_command_replace('#object-1', 'some html here'); * // Add a visual "changed" marker to the '#object-1' element. * $commands[] = ajax_command_changed('#object-1'); * // Menu 'page callback' and #ajax['callback'] functions are supposed to * // return render arrays. If returning an Ajax commands array, it must be * // encapsulated in a render array structure. * return array('#type' => 'ajax', '#commands' => $commands); * @endcode * * When returning an Ajax command array, it is often useful to have * status messages rendered along with other tasks in the command array. * In that case the the Ajax commands array may be constructed like this: * @code * $commands = array(); * $commands[] = ajax_command_replace(NULL, $output); * $commands[] = ajax_command_prepend(NULL, theme('status_messages')); * return array('#type' => 'ajax', '#commands' => $commands); * @endcode * * See @link ajax_commands Ajax framework commands @endlink */ /** * Renders a commands array into JSON. * * @param $commands * A list of macro commands generated by the use of ajax_command_*() * functions. */ function ajax_render($commands = array()) { // Ajax responses aren't rendered with html.tpl.php, so we have to call // drupal_get_css() and drupal_get_js() here, in order to have new files added // during this request to be loaded by the page. We only want to send back // files that the page hasn't already loaded, so we implement simple diffing // logic using array_diff_key(). foreach (array('css', 'js') as $type) { // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty, // since the base page ought to have at least one JS file and one CSS file // loaded. It probably indicates an error, and rather than making the page // reload all of the files, instead we return no new files. if (empty($_POST['ajax_page_state'][$type])) { $items[$type] = array(); } else { $function = 'drupal_add_' . $type; $items[$type] = $function(); drupal_alter($type, $items[$type]); // @todo Inline CSS and JS items are indexed numerically. These can't be // reliably diffed with array_diff_key(), since the number can change // due to factors unrelated to the inline content, so for now, we strip // the inline items from Ajax responses, and can add support for them // when drupal_add_css() and drupal_add_js() are changed to use a hash // of the inline content as the array key. foreach ($items[$type] as $key => $item) { if (is_numeric($key)) { unset($items[$type][$key]); } } // Ensure that the page doesn't reload what it already has. $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]); } } // Render the HTML to load these files, and add AJAX commands to insert this // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the // data from being altered again, as we already altered it above. Settings are // handled separately, afterwards. if (isset($items['js']['settings'])) { unset($items['js']['settings']); } $styles = drupal_get_css($items['css'], TRUE); $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); $scripts_header = drupal_get_js('header', $items['js'], TRUE); $extra_commands = array(); if (!empty($styles)) { $extra_commands[] = ajax_command_prepend('head', $styles); } if (!empty($scripts_header)) { $extra_commands[] = ajax_command_prepend('head', $scripts_header); } if (!empty($scripts_footer)) { $extra_commands[] = ajax_command_append('body', $scripts_footer); } if (!empty($extra_commands)) { $commands = array_merge($extra_commands, $commands); } // Now add a command to merge changes and additions to Drupal.settings. $scripts = drupal_add_js(); if (!empty($scripts['settings'])) { $settings = $scripts['settings']; array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE)); } // Allow modules to alter any Ajax response. drupal_alter('ajax_render', $commands); return drupal_json_encode($commands); } /** * Gets a form submitted via #ajax during an Ajax callback. * * This will load a form from the form cache used during Ajax operations. It * pulls the form info from $_POST. * * @return * An array containing the $form and $form_state. Use the list() function * to break these apart: * @code * list($form, $form_state, $form_id, $form_build_id) = ajax_get_form(); * @endcode */ function ajax_get_form() { $form_state = form_state_defaults(); $form_build_id = $_POST['form_build_id']; // Get the form from the cache. $form = form_get_cache($form_build_id, $form_state); if (!$form) { // If $form cannot be loaded from the cache, the form_build_id in $_POST // must be invalid, which means that someone performed a POST request onto // system/ajax without actually viewing the concerned form in the browser. // This is likely a hacking attempt as it never happens under normal // circumstances, so we just do nothing. watchdog('ajax', 'Invalid form POST data.', array(), WATCHDOG_WARNING); drupal_exit(); } // Since some of the submit handlers are run, redirects need to be disabled. $form_state['no_redirect'] = TRUE; // When a form is rebuilt after Ajax processing, its #build_id and #action // should not change. // @see drupal_rebuild_form() $form_state['rebuild_info']['copy']['#build_id'] = TRUE; $form_state['rebuild_info']['copy']['#action'] = TRUE; // The form needs to be processed; prepare for that by setting a few internal // variables. $form_state['input'] = $_POST; $form_id = $form['#form_id']; return array($form, $form_state, $form_id, $form_build_id); } /** * Menu callback; handles Ajax requests for the #ajax Form API property. * * This rebuilds the form from cache and invokes the defined #ajax['callback'] * to return an Ajax command structure for JavaScript. In case no 'callback' has * been defined, nothing will happen. * * The Form API #ajax property can be set both for buttons and other input * elements. * * This function is also the canonical example of how to implement * #ajax['path']. If processing is required that cannot be accomplished with * a callback, re-implement this function and set #ajax['path'] to the * enhanced function. * * @see system_menu() */ function ajax_form_callback() { list($form, $form_state) = ajax_get_form(); drupal_process_form($form['#form_id'], $form, $form_state); // We need to return the part of the form (or some other content) that needs // to be re-rendered so the browser can update the page with changed content. // Since this is the generic menu callback used by many Ajax elements, it is // up to the #ajax['callback'] function of the element (may or may not be a // button) that triggered the Ajax request to determine what needs to be // rendered. if (!empty($form_state['triggering_element'])) { $callback = $form_state['triggering_element']['#ajax']['callback']; } if (!empty($callback) && function_exists($callback)) { return $callback($form, $form_state); } } /** * Theme callback for Ajax requests. * * Many different pages can invoke an Ajax request to system/ajax or another * generic Ajax path. It is almost always desired for an Ajax response to be * rendered using the same theme as the base page, because most themes are built * with the assumption that they control the entire page, so if the CSS for two * themes are both loaded for a given page, they may conflict with each other. * For example, Bartik is Drupal's default theme, and Seven is Drupal's default * administration theme. Depending on whether the "Use the administration theme * when editing or creating content" checkbox is checked, the node edit form may * be displayed in either theme, but the Ajax response to the Field module's * "Add another item" button should be rendered using the same theme as the rest * of the page. Therefore, system_menu() sets the 'theme callback' for * 'system/ajax' to this function, and it is recommended that modules * implementing other generic Ajax paths do the same. * * @see system_menu() * @see file_menu() */ function ajax_base_page_theme() { if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) { $theme = $_POST['ajax_page_state']['theme']; $token = $_POST['ajax_page_state']['theme_token']; // Prevent a request forgery from giving a person access to a theme they // shouldn't be otherwise allowed to see. However, since everyone is allowed // to see the default theme, token validation isn't required for that, and // bypassing it allows most use-cases to work even when accessed from the // page cache. if ($theme === variable_get('theme_default', 'bartik') || drupal_valid_token($token, $theme)) { return $theme; } } } /** * Packages and sends the result of a page callback as an Ajax response. * * This function is the equivalent of drupal_deliver_html_page(), but for Ajax * requests. Like that function, it: * - Adds needed HTTP headers. * - Prints rendered output. * - Performs end-of-request tasks. * * @param $page_callback_result * The result of a page callback. Can be one of: * - NULL: to indicate no content. * - An integer menu status constant: to indicate an error condition. * - A string of HTML content. * - A renderable array of content. * * @see drupal_deliver_html_page() */ function ajax_deliver($page_callback_result) { // Browsers do not allow JavaScript to read the contents of a user's local // files. To work around that, the jQuery Form plugin submits forms containing // a file input element to an IFRAME, instead of using XHR. Browsers do not // normally expect JSON strings as content within an IFRAME, so the response // must be customized accordingly. // @see http://malsup.com/jquery/form/#file-upload // @see Drupal.ajax.prototype.beforeSend() $iframe_upload = !empty($_POST['ajax_iframe_upload']); // Emit a Content-Type HTTP header if none has been added by the page callback // or by a wrapping delivery callback. if (is_null(drupal_get_http_header('Content-Type'))) { if (!$iframe_upload) { // Standard JSON can be returned to a browser's XHR object, and to // non-browser user agents. // @see http://www.ietf.org/rfc/rfc4627.txt?number=4627 drupal_add_http_header('Content-Type', 'application/json; charset=utf-8'); } else { // Browser IFRAMEs expect HTML. With most other content types, Internet // Explorer presents the user with a download prompt. drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); } } // Print the response. $commands = ajax_prepare_response($page_callback_result); $json = ajax_render($commands); if (!$iframe_upload) { // Standard JSON can be returned to a browser's XHR object, and to // non-browser user agents. print $json; } else { // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into // links. This corrupts the JSON response. Protect the integrity of the // JSON data by making it the value of a textarea. // @see http://malsup.com/jquery/form/#file-upload // @see http://drupal.org/node/1009382 print ''; } // Perform end-of-request tasks. ajax_footer(); } /** * Converts the return value of a page callback into an Ajax commands array. * * @param $page_callback_result * The result of a page callback. Can be one of: * - NULL: to indicate no content. * - An integer menu status constant: to indicate an error condition. * - A string of HTML content. * - A renderable array of content. * * @return * An Ajax commands array that can be passed to ajax_render(). */ function ajax_prepare_response($page_callback_result) { $commands = array(); if (!isset($page_callback_result)) { // Simply delivering an empty commands array is sufficient. This results // in the Ajax request being completed, but nothing being done to the page. } elseif (is_int($page_callback_result)) { switch ($page_callback_result) { case MENU_NOT_FOUND: $commands[] = ajax_command_alert(t('The requested page could not be found.')); break; case MENU_ACCESS_DENIED: $commands[] = ajax_command_alert(t('You are not authorized to access this page.')); break; case MENU_SITE_OFFLINE: $commands[] = ajax_command_alert(filter_xss_admin(variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))); break; } } elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax')) { // Complex Ajax callbacks can return a result that contains an error message // or a specific set of commands to send to the browser. $page_callback_result += element_info('ajax'); $error = $page_callback_result['#error']; if (isset($error) && $error !== FALSE) { if ((empty($error) || $error === TRUE)) { $error = t('An error occurred while handling the request: The server received invalid input.'); } $commands[] = ajax_command_alert($error); } else { $commands = $page_callback_result['#commands']; } } else { // Like normal page callbacks, simple Ajax callbacks can return HTML // content, as a string or render array. This HTML is inserted in some // relationship to #ajax['wrapper'], as determined by which jQuery DOM // manipulation method is used. The method used is specified by // #ajax['method']. The default method is 'replaceWith', which completely // replaces the old wrapper element and its content with the new HTML. $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result); $commands[] = ajax_command_insert(NULL, $html); // Add the status messages inside the new content's wrapper element, so that // on subsequent Ajax requests, it is treated as old content. $commands[] = ajax_command_prepend(NULL, theme('status_messages')); } return $commands; } /** * Performs end-of-Ajax-request tasks. * * This function is the equivalent of drupal_page_footer(), but for Ajax * requests. * * @see drupal_page_footer() */ function ajax_footer() { // Even for Ajax requests, invoke hook_exit() implementations. There may be // modules that need very fast Ajax responses, and therefore, run Ajax // requests with an early bootstrap. if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) { module_invoke_all('exit'); } // Commit the user session. See above comment about the possibility of this // function running without session.inc loaded. if (function_exists('drupal_session_commit')) { drupal_session_commit(); } } /** * Form element processing handler for the #ajax form property. * * @param $element * An associative array containing the properties of the element. * * @return * The processed element. * * @see ajax_pre_render_element() */ function ajax_process_form($element, &$form_state) { $element = ajax_pre_render_element($element); if (!empty($element['#ajax_processed'])) { $form_state['cache'] = TRUE; } return $element; } /** * Adds Ajax information about an element to communicate with JavaScript. * * If #ajax['path'] is set on an element, this additional JavaScript is added * to the page header to attach the Ajax behaviors. See ajax.js for more * information. * * @param $element * An associative array containing the properties of the element. * Properties used: * - #ajax['event'] * - #ajax['prevent'] * - #ajax['path'] * - #ajax['options'] * - #ajax['wrapper'] * - #ajax['parameters'] * - #ajax['effect'] * * @return * The processed element with the necessary JavaScript attached to it. */ function ajax_pre_render_element($element) { // Skip already processed elements. if (isset($element['#ajax_processed'])) { return $element; } // Initialize #ajax_processed, so we do not process this element again. $element['#ajax_processed'] = FALSE; // Nothing to do if there is neither a callback nor a path. if (!(isset($element['#ajax']['callback']) || isset($element['#ajax']['path']))) { return $element; } // Add a reasonable default event handler if none was specified. if (isset($element['#ajax']) && !isset($element['#ajax']['event'])) { switch ($element['#type']) { case 'submit': case 'button': case 'image_button': // Pressing the ENTER key within a textfield triggers the click event of // the form's first submit button. Triggering Ajax in this situation // leads to problems, like breaking autocomplete textfields, so we bind // to mousedown instead of click. // @see http://drupal.org/node/216059 $element['#ajax']['event'] = 'mousedown'; // Retain keyboard accessibility by setting 'keypress'. This causes // ajax.js to trigger 'event' when SPACE or ENTER are pressed while the // button has focus. $element['#ajax']['keypress'] = TRUE; // Binding to mousedown rather than click means that it is possible to // trigger a click by pressing the mouse, holding the mouse button down // until the Ajax request is complete and the button is re-enabled, and // then releasing the mouse button. Set 'prevent' so that ajax.js binds // an additional handler to prevent such a click from triggering a // non-Ajax form submission. This also prevents a textfield's ENTER // press triggering this button's non-Ajax form submission behavior. if (!isset($element['#ajax']['prevent'])) { $element['#ajax']['prevent'] = 'click'; } break; case 'password': case 'textfield': case 'textarea': $element['#ajax']['event'] = 'blur'; break; case 'radio': case 'checkbox': case 'select': $element['#ajax']['event'] = 'change'; break; case 'link': $element['#ajax']['event'] = 'click'; break; default: return $element; } } // Attach JavaScript settings to the element. if (isset($element['#ajax']['event'])) { $element['#attached']['library'][] = array('system', 'jquery.form'); $element['#attached']['library'][] = array('system', 'drupal.ajax'); $settings = $element['#ajax']; // Assign default settings. $settings += array( 'path' => 'system/ajax', 'options' => array(), ); // @todo Legacy support. Remove in Drupal 8. if (isset($settings['method']) && $settings['method'] == 'replace') { $settings['method'] = 'replaceWith'; } // Change path to URL. $settings['url'] = url($settings['path'], $settings['options']); unset($settings['path'], $settings['options']); // Add special data to $settings['submit'] so that when this element // triggers an Ajax submission, Drupal's form processing can determine which // element triggered it. // @see _form_element_triggered_scripted_submission() if (isset($settings['trigger_as'])) { // An element can add a 'trigger_as' key within #ajax to make the element // submit as though another one (for example, a non-button can use this // to submit the form as though a button were clicked). When using this, // the 'name' key is always required to identify the element to trigger // as. The 'value' key is optional, and only needed when multiple elements // share the same name, which is commonly the case for buttons. $settings['submit']['_triggering_element_name'] = $settings['trigger_as']['name']; if (isset($settings['trigger_as']['value'])) { $settings['submit']['_triggering_element_value'] = $settings['trigger_as']['value']; } unset($settings['trigger_as']); } elseif (isset($element['#name'])) { // Most of the time, elements can submit as themselves, in which case the // 'trigger_as' key isn't needed, and the element's name is used. $settings['submit']['_triggering_element_name'] = $element['#name']; // If the element is a (non-image) button, its name may not identify it // uniquely, in which case a match on value is also needed. // @see _form_button_was_clicked() if (isset($element['#button_type']) && empty($element['#has_garbage_value'])) { $settings['submit']['_triggering_element_value'] = $element['#value']; } } // Convert a simple #ajax['progress'] string into an array. if (isset($settings['progress']) && is_string($settings['progress'])) { $settings['progress'] = array('type' => $settings['progress']); } // Change progress path to a full URL. if (isset($settings['progress']['path'])) { $settings['progress']['url'] = url($settings['progress']['path']); unset($settings['progress']['path']); } $element['#attached']['js'][] = array( 'type' => 'setting', 'data' => array('ajax' => array($element['#id'] => $settings)), ); // Indicate that Ajax processing was successful. $element['#ajax_processed'] = TRUE; } return $element; } /** * @} End of "defgroup ajax". */ /** * @defgroup ajax_commands Ajax framework commands * @{ * Functions to create various Ajax commands. * * These functions can be used to create arrays for use with the * ajax_render() function. */ /** * Creates a Drupal Ajax 'alert' command. * * The 'alert' command instructs the client to display a JavaScript alert * dialog box. * * This command is implemented by Drupal.ajax.prototype.commands.alert() * defined in misc/ajax.js. * * @param $text * The message string to display to the user. * * @return * An array suitable for use with the ajax_render() function. */ function ajax_command_alert($text) { return array( 'command' => 'alert', 'text' => $text, ); } /** * Creates a Drupal Ajax 'insert' command using the method in #ajax['method']. * * This command instructs the client to insert the given HTML using whichever * jQuery DOM manipulation method has been specified in the #ajax['method'] * variable of the element that triggered the request. * * This command is implemented by Drupal.ajax.prototype.commands.insert() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $html * The data to use with the jQuery method. * @param $settings * An optional array of settings that will be used for this command only. * * @return * An array suitable for use with the ajax_render() function. */ function ajax_command_insert($selector, $html, $settings = NULL) { return array( 'command' => 'insert', 'method' => NULL, 'selector' => $selector, 'data' => $html, 'settings' => $settings, ); } /** * Creates a Drupal Ajax 'insert/replaceWith' command. * * The 'insert/replaceWith' command instructs the client to use jQuery's * replaceWith() method to replace each element matched matched by the given * selector with the given HTML. * * This command is implemented by Drupal.ajax.prototype.commands.insert() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $html * The data to use with the jQuery replaceWith() method. * @param $settings * An optional array of settings that will be used for this command only. * * @return * An array suitable for use with the ajax_render() function. * * See * @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink */ function ajax_command_replace($selector, $html, $settings = NULL) { return array( 'command' => 'insert', 'method' => 'replaceWith', 'selector' => $selector, 'data' => $html, 'settings' => $settings, ); } /** * Creates a Drupal Ajax 'insert/html' command. * * The 'insert/html' command instructs the client to use jQuery's html() * method to set the HTML content of each element matched by the given * selector while leaving the outer tags intact. * * This command is implemented by Drupal.ajax.prototype.commands.insert() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $html * The data to use with the jQuery html() method. * @param $settings * An optional array of settings that will be used for this command only. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/Attributes/html#val */ function ajax_command_html($selector, $html, $settings = NULL) { return array( 'command' => 'insert', 'method' => 'html', 'selector' => $selector, 'data' => $html, 'settings' => $settings, ); } /** * Creates a Drupal Ajax 'insert/prepend' command. * * The 'insert/prepend' command instructs the client to use jQuery's prepend() * method to prepend the given HTML content to the inside each element matched * by the given selector. * * This command is implemented by Drupal.ajax.prototype.commands.insert() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $html * The data to use with the jQuery prepend() method. * @param $settings * An optional array of settings that will be used for this command only. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/Manipulation/prepend#content */ function ajax_command_prepend($selector, $html, $settings = NULL) { return array( 'command' => 'insert', 'method' => 'prepend', 'selector' => $selector, 'data' => $html, 'settings' => $settings, ); } /** * Creates a Drupal Ajax 'insert/append' command. * * The 'insert/append' command instructs the client to use jQuery's append() * method to append the given HTML content to the inside of each element matched * by the given selector. * * This command is implemented by Drupal.ajax.prototype.commands.insert() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $html * The data to use with the jQuery append() method. * @param $settings * An optional array of settings that will be used for this command only. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/Manipulation/append#content */ function ajax_command_append($selector, $html, $settings = NULL) { return array( 'command' => 'insert', 'method' => 'append', 'selector' => $selector, 'data' => $html, 'settings' => $settings, ); } /** * Creates a Drupal Ajax 'insert/after' command. * * The 'insert/after' command instructs the client to use jQuery's after() * method to insert the given HTML content after each element matched by * the given selector. * * This command is implemented by Drupal.ajax.prototype.commands.insert() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $html * The data to use with the jQuery after() method. * @param $settings * An optional array of settings that will be used for this command only. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/Manipulation/after#content */ function ajax_command_after($selector, $html, $settings = NULL) { return array( 'command' => 'insert', 'method' => 'after', 'selector' => $selector, 'data' => $html, 'settings' => $settings, ); } /** * Creates a Drupal Ajax 'insert/before' command. * * The 'insert/before' command instructs the client to use jQuery's before() * method to insert the given HTML content before each of elements matched by * the given selector. * * This command is implemented by Drupal.ajax.prototype.commands.insert() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $html * The data to use with the jQuery before() method. * @param $settings * An optional array of settings that will be used for this command only. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/Manipulation/before#content */ function ajax_command_before($selector, $html, $settings = NULL) { return array( 'command' => 'insert', 'method' => 'before', 'selector' => $selector, 'data' => $html, 'settings' => $settings, ); } /** * Creates a Drupal Ajax 'remove' command. * * The 'remove' command instructs the client to use jQuery's remove() method * to remove each of elements matched by the given selector, and everything * within them. * * This command is implemented by Drupal.ajax.prototype.commands.remove() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/Manipulation/remove#expr */ function ajax_command_remove($selector) { return array( 'command' => 'remove', 'selector' => $selector, ); } /** * Creates a Drupal Ajax 'changed' command. * * This command instructs the client to mark each of the elements matched by the * given selector as 'ajax-changed'. * * This command is implemented by Drupal.ajax.prototype.commands.changed() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $asterisk * An optional CSS selector which must be inside $selector. If specified, * an asterisk will be appended to the HTML inside the $asterisk selector. * * @return * An array suitable for use with the ajax_render() function. */ function ajax_command_changed($selector, $asterisk = '') { return array( 'command' => 'changed', 'selector' => $selector, 'asterisk' => $asterisk, ); } /** * Creates a Drupal Ajax 'css' command. * * The 'css' command will instruct the client to use the jQuery css() method * to apply the CSS arguments to elements matched by the given selector. * * This command is implemented by Drupal.ajax.prototype.commands.css() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $argument * An array of key/value pairs to set in the CSS for the selector. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/CSS/css#properties */ function ajax_command_css($selector, $argument) { return array( 'command' => 'css', 'selector' => $selector, 'argument' => $argument, ); } /** * Creates a Drupal Ajax 'settings' command. * * The 'settings' command instructs the client either to use the given array as * the settings for ajax-loaded content or to extend Drupal.settings with the * given array, depending on the value of the $merge parameter. * * This command is implemented by Drupal.ajax.prototype.commands.settings() * defined in misc/ajax.js. * * @param $argument * An array of key/value pairs to add to the settings. This will be utilized * for all commands after this if they do not include their own settings * array. * @param $merge * Whether or not the passed settings in $argument should be merged into the * global Drupal.settings on the page. By default (FALSE), the settings that * are passed to Drupal.attachBehaviors will not include the global * Drupal.settings. * * @return * An array suitable for use with the ajax_render() function. */ function ajax_command_settings($argument, $merge = FALSE) { return array( 'command' => 'settings', 'settings' => $argument, 'merge' => $merge, ); } /** * Creates a Drupal Ajax 'data' command. * * The 'data' command instructs the client to attach the name=value pair of * data to the selector via jQuery's data cache. * * This command is implemented by Drupal.ajax.prototype.commands.data() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $name * The name or key (in the key value pair) of the data attached to this * selector. * @param $value * The value of the data. Not just limited to strings can be any format. * * @return * An array suitable for use with the ajax_render() function. * * @see http://docs.jquery.com/Core/data#namevalue */ function ajax_command_data($selector, $name, $value) { return array( 'command' => 'data', 'selector' => $selector, 'name' => $name, 'value' => $value, ); } /** * Creates a Drupal Ajax 'invoke' command. * * The 'invoke' command will instruct the client to invoke the given jQuery * method with the supplied arguments on the elements matched by the given * selector. Intended for simple jQuery commands, such as attr(), addClass(), * removeClass(), toggleClass(), etc. * * This command is implemented by Drupal.ajax.prototype.commands.invoke() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. If the command is a response to a request from * an #ajax form element then this value can be NULL. * @param $method * The jQuery method to invoke. * @param $arguments * (optional) A list of arguments to the jQuery $method, if any. * * @return * An array suitable for use with the ajax_render() function. */ function ajax_command_invoke($selector, $method, array $arguments = array()) { return array( 'command' => 'invoke', 'selector' => $selector, 'method' => $method, 'arguments' => $arguments, ); } /** * Creates a Drupal Ajax 'restripe' command. * * The 'restripe' command instructs the client to restripe a table. This is * usually used after a table has been modified by a replace or append command. * * This command is implemented by Drupal.ajax.prototype.commands.restripe() * defined in misc/ajax.js. * * @param $selector * A jQuery selector string. * * @return * An array suitable for use with the ajax_render() function. */ function ajax_command_restripe($selector) { return array( 'command' => 'restripe', 'selector' => $selector, ); } drupal-7.26/includes/unicode.inc0000644001412200141220000005406712265562324016163 0ustar benderbenderPHP mbstring extension for improved Unicode support.', array('@url' => 'http://www.php.net/mbstring'))); } // Check mbstring configuration if (ini_get('mbstring.func_overload') != 0) { return array(UNICODE_ERROR, $t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini mbstring.func_overload setting. Please refer to the PHP mbstring documentation for more information.', array('@url' => 'http://www.php.net/mbstring'))); } if (ini_get('mbstring.encoding_translation') != 0) { return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini mbstring.encoding_translation setting. Please refer to the PHP mbstring documentation for more information.', array('@url' => 'http://www.php.net/mbstring'))); } if (ini_get('mbstring.http_input') != 'pass') { return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini mbstring.http_input setting. Please refer to the PHP mbstring documentation for more information.', array('@url' => 'http://www.php.net/mbstring'))); } if (ini_get('mbstring.http_output') != 'pass') { return array(UNICODE_ERROR, $t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini mbstring.http_output setting. Please refer to the PHP mbstring documentation for more information.', array('@url' => 'http://www.php.net/mbstring'))); } // Set appropriate configuration mb_internal_encoding('utf-8'); mb_language('uni'); return array(UNICODE_MULTIBYTE, ''); } /** * Returns Unicode library status and errors. */ function unicode_requirements() { // Ensure translations don't break during installation. $t = get_t(); $libraries = array( UNICODE_SINGLEBYTE => $t('Standard PHP'), UNICODE_MULTIBYTE => $t('PHP Mbstring Extension'), UNICODE_ERROR => $t('Error'), ); $severities = array( UNICODE_SINGLEBYTE => REQUIREMENT_WARNING, UNICODE_MULTIBYTE => REQUIREMENT_OK, UNICODE_ERROR => REQUIREMENT_ERROR, ); list($library, $description) = _unicode_check(); $requirements['unicode'] = array( 'title' => $t('Unicode library'), 'value' => $libraries[$library], ); if ($description) { $requirements['unicode']['description'] = $description; } $requirements['unicode']['severity'] = $severities[$library]; return $requirements; } /** * Prepares a new XML parser. * * This is a wrapper around xml_parser_create() which extracts the encoding * from the XML data first and sets the output encoding to UTF-8. This function * should be used instead of xml_parser_create(), because PHP 4's XML parser * doesn't check the input encoding itself. "Starting from PHP 5, the input * encoding is automatically detected, so that the encoding parameter specifies * only the output encoding." * * This is also where unsupported encodings will be converted. Callers should * take this into account: $data might have been changed after the call. * * @param $data * The XML data which will be parsed later. * * @return * An XML parser object or FALSE on error. * * @ingroup php_wrappers */ function drupal_xml_parser_create(&$data) { // Default XML encoding is UTF-8 $encoding = 'utf-8'; $bom = FALSE; // Check for UTF-8 byte order mark (PHP5's XML parser doesn't handle it). if (!strncmp($data, "\xEF\xBB\xBF", 3)) { $bom = TRUE; $data = substr($data, 3); } // Check for an encoding declaration in the XML prolog if no BOM was found. if (!$bom && preg_match('/^<\?xml[^>]+encoding="(.+?)"/', $data, $match)) { $encoding = $match[1]; } // Unsupported encodings are converted here into UTF-8. $php_supported = array('utf-8', 'iso-8859-1', 'us-ascii'); if (!in_array(strtolower($encoding), $php_supported)) { $out = drupal_convert_to_utf8($data, $encoding); if ($out !== FALSE) { $encoding = 'utf-8'; $data = preg_replace('/^(<\?xml[^>]+encoding)="(.+?)"/', '\\1="utf-8"', $out); } else { watchdog('php', 'Could not convert XML encoding %s to UTF-8.', array('%s' => $encoding), WATCHDOG_WARNING); return FALSE; } } $xml_parser = xml_parser_create($encoding); xml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, 'utf-8'); return $xml_parser; } /** * Converts data to UTF-8. * * Requires the iconv, GNU recode or mbstring PHP extension. * * @param $data * The data to be converted. * @param $encoding * The encoding that the data is in. * * @return * Converted data or FALSE. */ function drupal_convert_to_utf8($data, $encoding) { if (function_exists('iconv')) { $out = @iconv($encoding, 'utf-8', $data); } elseif (function_exists('mb_convert_encoding')) { $out = @mb_convert_encoding($data, 'utf-8', $encoding); } elseif (function_exists('recode_string')) { $out = @recode_string($encoding . '..utf-8', $data); } else { watchdog('php', 'Unsupported encoding %s. Please install iconv, GNU recode or mbstring for PHP.', array('%s' => $encoding), WATCHDOG_ERROR); return FALSE; } return $out; } /** * Truncates a UTF-8-encoded string safely to a number of bytes. * * If the end position is in the middle of a UTF-8 sequence, it scans backwards * until the beginning of the byte sequence. * * Use this function whenever you want to chop off a string at an unsure * location. On the other hand, if you're sure that you're splitting on a * character boundary (e.g. after using strpos() or similar), you can safely * use substr() instead. * * @param $string * The string to truncate. * @param $len * An upper limit on the returned string length. * * @return * The truncated string. */ function drupal_truncate_bytes($string, $len) { if (strlen($string) <= $len) { return $string; } if ((ord($string[$len]) < 0x80) || (ord($string[$len]) >= 0xC0)) { return substr($string, 0, $len); } // Scan backwards to beginning of the byte sequence. while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0); return substr($string, 0, $len); } /** * Truncates a UTF-8-encoded string safely to a number of characters. * * @param $string * The string to truncate. * @param $max_length * An upper limit on the returned string length, including trailing ellipsis * if $add_ellipsis is TRUE. * @param $wordsafe * If TRUE, attempt to truncate on a word boundary. Word boundaries are * spaces, punctuation, and Unicode characters used as word boundaries in * non-Latin languages; see PREG_CLASS_UNICODE_WORD_BOUNDARY for more * information. If a word boundary cannot be found that would make the length * of the returned string fall within length guidelines (see parameters * $max_length and $min_wordsafe_length), word boundaries are ignored. * @param $add_ellipsis * If TRUE, add t('...') to the end of the truncated string (defaults to * FALSE). The string length will still fall within $max_length. * @param $min_wordsafe_length * If $wordsafe is TRUE, the minimum acceptable length for truncation (before * adding an ellipsis, if $add_ellipsis is TRUE). Has no effect if $wordsafe * is FALSE. This can be used to prevent having a very short resulting string * that will not be understandable. For instance, if you are truncating the * string "See myverylongurlexample.com for more information" to a word-safe * return length of 20, the only available word boundary within 20 characters * is after the word "See", which wouldn't leave a very informative string. If * you had set $min_wordsafe_length to 10, though, the function would realise * that "See" alone is too short, and would then just truncate ignoring word * boundaries, giving you "See myverylongurl..." (assuming you had set * $add_ellipses to TRUE). * * @return string * The truncated string. */ function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) { $ellipsis = ''; $max_length = max($max_length, 0); $min_wordsafe_length = max($min_wordsafe_length, 0); if (drupal_strlen($string) <= $max_length) { // No truncation needed, so don't add ellipsis, just return. return $string; } if ($add_ellipsis) { // Truncate ellipsis in case $max_length is small. $ellipsis = drupal_substr(t('...'), 0, $max_length); $max_length -= drupal_strlen($ellipsis); $max_length = max($max_length, 0); } if ($max_length <= $min_wordsafe_length) { // Do not attempt word-safe if lengths are bad. $wordsafe = FALSE; } if ($wordsafe) { $matches = array(); // Find the last word boundary, if there is one within $min_wordsafe_length // to $max_length characters. preg_match() is always greedy, so it will // find the longest string possible. $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']/u', $string, $matches); if ($found) { $string = $matches[1]; } else { $string = drupal_substr($string, 0, $max_length); } } else { $string = drupal_substr($string, 0, $max_length); } if ($add_ellipsis) { $string .= $ellipsis; } return $string; } /** * Encodes MIME/HTTP header values that contain incorrectly encoded characters. * * For example, mime_header_encode('tést.txt') returns "=?UTF-8?B?dMOpc3QudHh0?=". * * See http://www.rfc-editor.org/rfc/rfc2047.txt for more information. * * Notes: * - Only encode strings that contain non-ASCII characters. * - We progressively cut-off a chunk with truncate_utf8(). This is to ensure * each chunk starts and ends on a character boundary. * - Using \n as the chunk separator may cause problems on some systems and may * have to be changed to \r\n or \r. * * @param $string * The header to encode. * * @return string * The mime-encoded header. * * @see mime_header_decode() */ function mime_header_encode($string) { if (preg_match('/[^\x20-\x7E]/', $string)) { $chunk_size = 47; // floor((75 - strlen("=?UTF-8?B??=")) * 0.75); $len = strlen($string); $output = ''; while ($len > 0) { $chunk = drupal_truncate_bytes($string, $chunk_size); $output .= ' =?UTF-8?B?' . base64_encode($chunk) . "?=\n"; $c = strlen($chunk); $string = substr($string, $c); $len -= $c; } return trim($output); } return $string; } /** * Decodes MIME/HTTP encoded header values. * * @param $header * The header to decode. * * @return string * The mime-decoded header. * * @see mime_header_encode() */ function mime_header_decode($header) { // First step: encoded chunks followed by other encoded chunks (need to collapse whitespace) $header = preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=\s+(?==\?)/', '_mime_header_decode', $header); // Second step: remaining chunks (do not collapse whitespace) return preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=/', '_mime_header_decode', $header); } /** * Decodes encoded header data passed from mime_header_decode(). * * Callback for preg_replace_callback() within mime_header_decode(). * * @param $matches * The array of matches from preg_replace_callback(). * * @return string * The mime-decoded string. * * @see mime_header_decode() */ function _mime_header_decode($matches) { // Regexp groups: // 1: Character set name // 2: Escaping method (Q or B) // 3: Encoded data $data = ($matches[2] == 'B') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3])); if (strtolower($matches[1]) != 'utf-8') { $data = drupal_convert_to_utf8($data, $matches[1]); } return $data; } /** * Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes. * * Double-escaped entities will only be decoded once ("&lt;" becomes "<" * , not "<"). Be careful when using this function, as decode_entities can * revert previous sanitization efforts (<script> will become '))); * * // The statement below demonstrates dangerous use of drupal_attributes, and * // will return an onmouseout attribute with JavaScript code that, when used * // as attribute in a tag, will cause users to be redirected to another site. * // * // In this case, the 'onmouseout' attribute should not be whitelisted -- * // you don't want users to have the ability to add this attribute or others * // that take JavaScript commands. * drupal_attributes(array('onmouseout' => 'window.location="http://malicious.com/";'))); * @endcode * * @param $attributes * An associative array of key-value pairs to be converted to attributes. * * @return * A string ready for insertion in a tag (starts with a space). * * @ingroup sanitization */ function drupal_attributes(array $attributes = array()) { foreach ($attributes as $attribute => &$data) { $data = implode(' ', (array) $data); $data = $attribute . '="' . check_plain($data) . '"'; } return $attributes ? ' ' . implode(' ', $attributes) : ''; } /** * Formats an internal or external URL link as an HTML anchor tag. * * This function correctly handles aliased paths and adds an 'active' class * attribute to links that point to the current page (for theming), so all * internal links output by modules should be generated by this function if * possible. * * However, for links enclosed in translatable text you should use t() and * embed the HTML anchor tag directly in the translated string. For example: * @code * t('Visit the settings page', array('@url' => url('admin'))); * @endcode * This keeps the context of the link title ('settings' in the example) for * translators. * * @param string $text * The translated link text for the anchor tag. * @param string $path * The internal path or external URL being linked to, such as "node/34" or * "http://example.com/foo". After the url() function is called to construct * the URL from $path and $options, the resulting URL is passed through * check_plain() before it is inserted into the HTML anchor tag, to ensure * well-formed HTML. See url() for more information and notes. * @param array $options * An associative array of additional options. Defaults to an empty array. It * may contain the following elements. * - 'attributes': An associative array of HTML attributes to apply to the * anchor tag. If element 'class' is included, it must be an array; 'title' * must be a string; other elements are more flexible, as they just need * to work in a call to drupal_attributes($options['attributes']). * - 'html' (default FALSE): Whether $text is HTML or just plain-text. For * example, to make an image tag into a link, this must be set to TRUE, or * you will see the escaped HTML image tag. $text is not sanitized if * 'html' is TRUE. The calling function must ensure that $text is already * safe. * - 'language': An optional language object. If the path being linked to is * internal to the site, $options['language'] is used to determine whether * the link is "active", or pointing to the current page (the language as * well as the path must match). This element is also used by url(). * - Additional $options elements used by the url() function. * * @return string * An HTML string containing a link to the given path. * * @see url() */ function l($text, $path, array $options = array()) { global $language_url; static $use_theme = NULL; // Merge in defaults. $options += array( 'attributes' => array(), 'html' => FALSE, ); // Append active class. if (($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) && (empty($options['language']) || $options['language']->language == $language_url->language)) { $options['attributes']['class'][] = 'active'; } // Remove all HTML and PHP tags from a tooltip. For best performance, we act only // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive). if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) { $options['attributes']['title'] = strip_tags($options['attributes']['title']); } // Determine if rendering of the link is to be done with a theme function // or the inline default. Inline is faster, but if the theme system has been // loaded and a module or theme implements a preprocess or process function // or overrides the theme_link() function, then invoke theme(). Preliminary // benchmarks indicate that invoking theme() can slow down the l() function // by 20% or more, and that some of the link-heavy Drupal pages spend more // than 10% of the total page request time in the l() function. if (!isset($use_theme) && function_exists('theme')) { // Allow edge cases to prevent theme initialization and force inline link // rendering. if (variable_get('theme_link', TRUE)) { drupal_theme_initialize(); $registry = theme_get_registry(FALSE); // We don't want to duplicate functionality that's in theme(), so any // hint of a module or theme doing anything at all special with the 'link' // theme hook should simply result in theme() being called. This includes // the overriding of theme_link() with an alternate function or template, // the presence of preprocess or process functions, or the presence of // include files. $use_theme = !isset($registry['link']['function']) || ($registry['link']['function'] != 'theme_link'); $use_theme = $use_theme || !empty($registry['link']['preprocess functions']) || !empty($registry['link']['process functions']) || !empty($registry['link']['includes']); } else { $use_theme = FALSE; } } if ($use_theme) { return theme('link', array('text' => $text, 'path' => $path, 'options' => $options)); } // The result of url() is a plain-text URL. Because we are using it here // in an HTML argument context, we need to encode it properly. return '' . ($options['html'] ? $text : check_plain($text)) . ''; } /** * Delivers a page callback result to the browser in the appropriate format. * * This function is most commonly called by menu_execute_active_handler(), but * can also be called by error conditions such as drupal_not_found(), * drupal_access_denied(), and drupal_site_offline(). * * When a user requests a page, index.php calls menu_execute_active_handler(), * which calls the 'page callback' function registered in hook_menu(). The page * callback function can return one of: * - NULL: to indicate no content. * - An integer menu status constant: to indicate an error condition. * - A string of HTML content. * - A renderable array of content. * Returning a renderable array rather than a string of HTML is preferred, * because that provides modules with more flexibility in customizing the final * result. * * When the page callback returns its constructed content to * menu_execute_active_handler(), this function gets called. The purpose of * this function is to determine the most appropriate 'delivery callback' * function to route the content to. The delivery callback function then * sends the content to the browser in the needed format. The default delivery * callback is drupal_deliver_html_page(), which delivers the content as an HTML * page, complete with blocks in addition to the content. This default can be * overridden on a per menu router item basis by setting 'delivery callback' in * hook_menu() or hook_menu_alter(), and can also be overridden on a per request * basis in hook_page_delivery_callback_alter(). * * For example, the same page callback function can be used for an HTML * version of the page and an Ajax version of the page. The page callback * function just needs to decide what content is to be returned and the * delivery callback function will send it as an HTML page or an Ajax * response, as appropriate. * * In order for page callbacks to be reusable in different delivery formats, * they should not issue any "print" or "echo" statements, but instead just * return content. * * Also note that this function does not perform access checks. The delivery * callback function specified in hook_menu(), hook_menu_alter(), or * hook_page_delivery_callback_alter() will be called even if the router item * access checks fail. This is intentional (it is needed for JSON and other * purposes), but it has security implications. Do not call this function * directly unless you understand the security implications, and be careful in * writing delivery callbacks, so that they do not violate security. See * drupal_deliver_html_page() for an example of a delivery callback that * respects security. * * @param $page_callback_result * The result of a page callback. Can be one of: * - NULL: to indicate no content. * - An integer menu status constant: to indicate an error condition. * - A string of HTML content. * - A renderable array of content. * @param $default_delivery_callback * (Optional) If given, it is the name of a delivery function most likely * to be appropriate for the page request as determined by the calling * function (e.g., menu_execute_active_handler()). If not given, it is * determined from the menu router information of the current page. * * @see menu_execute_active_handler() * @see hook_menu() * @see hook_menu_alter() * @see hook_page_delivery_callback_alter() */ function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) { if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) { $default_delivery_callback = $router_item['delivery_callback']; } $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page'; // Give modules a chance to alter the delivery callback used, based on // request-time context (e.g., HTTP request headers). drupal_alter('page_delivery_callback', $delivery_callback); if (function_exists($delivery_callback)) { $delivery_callback($page_callback_result); } else { // If a delivery callback is specified, but doesn't exist as a function, // something is wrong, but don't print anything, since it's not known // what format the response needs to be in. watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR); } } /** * Packages and sends the result of a page callback to the browser as HTML. * * @param $page_callback_result * The result of a page callback. Can be one of: * - NULL: to indicate no content. * - An integer menu status constant: to indicate an error condition. * - A string of HTML content. * - A renderable array of content. * * @see drupal_deliver_page() */ function drupal_deliver_html_page($page_callback_result) { // Emit the correct charset HTTP header, but not if the page callback // result is NULL, since that likely indicates that it printed something // in which case, no further headers may be sent, and not if code running // for this page request has already set the content type header. if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) { drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); } // Send appropriate HTTP-Header for browsers and search engines. global $language; drupal_add_http_header('Content-Language', $language->language); // Menu status constants are integers; page content is a string or array. if (is_int($page_callback_result)) { // @todo: Break these up into separate functions? switch ($page_callback_result) { case MENU_NOT_FOUND: // Print a 404 page. drupal_add_http_header('Status', '404 Not Found'); watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); // Check for and return a fast 404 page if configured. drupal_fast_404(); // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { $_GET['destination'] = $_GET['q']; } $path = drupal_get_normal_path(variable_get('site_404', '')); if ($path && $path != $_GET['q']) { // Custom 404 handler. Set the active item in case there are tabs to // display, or other dependencies on the path. menu_set_active_item($path); $return = menu_execute_active_handler($path, FALSE); } if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { // Standard 404 handler. drupal_set_title(t('Page not found')); $return = t('The requested page "@path" could not be found.', array('@path' => request_uri())); } drupal_set_page_content($return); $page = element_info('page'); print drupal_render_page($page); break; case MENU_ACCESS_DENIED: // Print a 403 page. drupal_add_http_header('Status', '403 Forbidden'); watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { $_GET['destination'] = $_GET['q']; } $path = drupal_get_normal_path(variable_get('site_403', '')); if ($path && $path != $_GET['q']) { // Custom 403 handler. Set the active item in case there are tabs to // display or other dependencies on the path. menu_set_active_item($path); $return = menu_execute_active_handler($path, FALSE); } if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { // Standard 403 handler. drupal_set_title(t('Access denied')); $return = t('You are not authorized to access this page.'); } print drupal_render_page($return); break; case MENU_SITE_OFFLINE: // Print a 503 page. drupal_maintenance_theme(); drupal_add_http_header('Status', '503 Service unavailable'); drupal_set_title(t('Site under maintenance')); print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))))); break; } } elseif (isset($page_callback_result)) { // Print anything besides a menu constant, assuming it's not NULL or // undefined. print drupal_render_page($page_callback_result); } // Perform end-of-request tasks. drupal_page_footer(); } /** * Performs end-of-request tasks. * * This function sets the page cache if appropriate, and allows modules to * react to the closing of the page by calling hook_exit(). */ function drupal_page_footer() { global $user; module_invoke_all('exit'); // Commit the user session, if needed. drupal_session_commit(); if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) { drupal_serve_page_from_cache($cache); } else { ob_flush(); } _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); drupal_cache_system_paths(); module_implements_write_cache(); system_run_automated_cron(); } /** * Performs end-of-request tasks. * * In some cases page requests need to end without calling drupal_page_footer(). * In these cases, call drupal_exit() instead. There should rarely be a reason * to call exit instead of drupal_exit(); * * @param $destination * If this function is called from drupal_goto(), then this argument * will be a fully-qualified URL that is the destination of the redirect. * This should be passed along to hook_exit() implementations. */ function drupal_exit($destination = NULL) { if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) { if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { module_invoke_all('exit', $destination); } drupal_session_commit(); } exit; } /** * Forms an associative array from a linear array. * * This function walks through the provided array and constructs an associative * array out of it. The keys of the resulting array will be the values of the * input array. The values will be the same as the keys unless a function is * specified, in which case the output of the function is used for the values * instead. * * @param $array * A linear array. * @param $function * A name of a function to apply to all values before output. * * @return * An associative array. */ function drupal_map_assoc($array, $function = NULL) { // array_combine() fails with empty arrays: // http://bugs.php.net/bug.php?id=34857. $array = !empty($array) ? array_combine($array, $array) : array(); if (is_callable($function)) { $array = array_map($function, $array); } return $array; } /** * Attempts to set the PHP maximum execution time. * * This function is a wrapper around the PHP function set_time_limit(). * When called, set_time_limit() restarts the timeout counter from zero. * In other words, if the timeout is the default 30 seconds, and 25 seconds * into script execution a call such as set_time_limit(20) is made, the * script will run for a total of 45 seconds before timing out. * * It also means that it is possible to decrease the total time limit if * the sum of the new time limit and the current time spent running the * script is inferior to the original time limit. It is inherent to the way * set_time_limit() works, it should rather be called with an appropriate * value every time you need to allocate a certain amount of time * to execute a task than only once at the beginning of the script. * * Before calling set_time_limit(), we check if this function is available * because it could be disabled by the server administrator. We also hide all * the errors that could occur when calling set_time_limit(), because it is * not possible to reliably ensure that PHP or a security extension will * not issue a warning/error if they prevent the use of this function. * * @param $time_limit * An integer specifying the new time limit, in seconds. A value of 0 * indicates unlimited execution time. * * @ingroup php_wrappers */ function drupal_set_time_limit($time_limit) { if (function_exists('set_time_limit')) { @set_time_limit($time_limit); } } /** * Returns the path to a system item (module, theme, etc.). * * @param $type * The type of the item (i.e. theme, theme_engine, module, profile). * @param $name * The name of the item for which the path is requested. * * @return * The path to the requested item or an empty string if the item is not found. */ function drupal_get_path($type, $name) { return dirname(drupal_get_filename($type, $name)); } /** * Returns the base URL path (i.e., directory) of the Drupal installation. * * base_path() adds a "/" to the beginning and end of the returned path if the * path is not empty. At the very least, this will return "/". * * Examples: * - http://example.com returns "/" because the path is empty. * - http://example.com/drupal/folder returns "/drupal/folder/". */ function base_path() { return $GLOBALS['base_path']; } /** * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD. * * This function can be called as long the HTML header hasn't been sent, which * on normal pages is up through the preprocess step of theme('html'). Adding * a link will overwrite a prior link with the exact same 'rel' and 'href' * attributes. * * @param $attributes * Associative array of element attributes including 'href' and 'rel'. * @param $header * Optional flag to determine if a HTTP 'Link:' header should be sent. */ function drupal_add_html_head_link($attributes, $header = FALSE) { $element = array( '#tag' => 'link', '#attributes' => $attributes, ); $href = $attributes['href']; if ($header) { // Also add a HTTP header "Link:". $href = '<' . check_plain($attributes['href']) . '>;'; unset($attributes['href']); $element['#attached']['drupal_add_http_header'][] = array('Link', $href . drupal_http_header_attributes($attributes), TRUE); } drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . $href); } /** * Adds a cascading stylesheet to the stylesheet queue. * * Calling drupal_static_reset('drupal_add_css') will clear all cascading * stylesheets added so far. * * If CSS aggregation/compression is enabled, all cascading style sheets added * with $options['preprocess'] set to TRUE will be merged into one aggregate * file and compressed by removing all extraneous white space. * Preprocessed inline stylesheets will not be aggregated into this single file; * instead, they are just compressed upon output on the page. Externally hosted * stylesheets are never aggregated or compressed. * * The reason for aggregating the files is outlined quite thoroughly here: * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due * to request overhead, one bigger file just loads faster than two smaller ones * half its size." * * $options['preprocess'] should be only set to TRUE when a file is required for * all typical visitors and most pages of a site. It is critical that all * preprocessed files are added unconditionally on every page, even if the * files do not happen to be needed on a page. This is normally done by calling * drupal_add_css() in a hook_init() implementation. * * Non-preprocessed files should only be added to the page when they are * actually needed. * * @param $data * (optional) The stylesheet data to be added, depending on what is passed * through to the $options['type'] parameter: * - 'file': The path to the CSS file relative to the base_path(), or a * stream wrapper URI. For example: "modules/devel/devel.css" or * "public://generated_css/stylesheet_1.css". Note that Modules should * always prefix the names of their CSS files with the module name; for * example, system-menus.css rather than simply menus.css. Themes can * override module-supplied CSS files based on their filenames, and this * prefixing helps prevent confusing name collisions for theme developers. * See drupal_get_css() where the overrides are performed. Also, if the * direction of the current language is right-to-left (Hebrew, Arabic, * etc.), the function will also look for an RTL CSS file and append it to * the list. The name of this file should have an '-rtl.css' suffix. For * example, a CSS file called 'mymodule-name.css' will have a * 'mymodule-name-rtl.css' file added to the list, if exists in the same * directory. This CSS file should contain overrides for properties which * should be reversed or otherwise different in a right-to-left display. * - 'inline': A string of CSS that should be placed in the given scope. Note * that it is better practice to use 'file' stylesheets, rather than * 'inline', as the CSS would then be aggregated and cached. * - 'external': The absolute path to an external CSS file that is not hosted * on the local server. These files will not be aggregated if CSS * aggregation is enabled. * @param $options * (optional) A string defining the 'type' of CSS that is being added in the * $data parameter ('file', 'inline', or 'external'), or an array which can * have any or all of the following keys: * - 'type': The type of stylesheet being added. Available options are 'file', * 'inline' or 'external'. Defaults to 'file'. * - 'basename': Force a basename for the file being added. Modules are * expected to use stylesheets with unique filenames, but integration of * external libraries may make this impossible. The basename of * 'modules/node/node.css' is 'node.css'. If the external library "node.js" * ships with a 'node.css', then a different, unique basename would be * 'node.js.css'. * - 'group': A number identifying the group in which to add the stylesheet. * Available constants are: * - CSS_SYSTEM: Any system-layer CSS. * - CSS_DEFAULT: (default) Any module-layer CSS. * - CSS_THEME: Any theme-layer CSS. * The group number serves as a weight: the markup for loading a stylesheet * within a lower weight group is output to the page before the markup for * loading a stylesheet within a higher weight group, so CSS within higher * weight groups take precendence over CSS within lower weight groups. * - 'every_page': For optimal front-end performance when aggregation is * enabled, this should be set to TRUE if the stylesheet is present on every * page of the website for users for whom it is present at all. This * defaults to FALSE. It is set to TRUE for stylesheets added via module and * theme .info files. Modules that add stylesheets within hook_init() * implementations, or from other code that ensures that the stylesheet is * added to all website pages, should also set this flag to TRUE. All * stylesheets within the same group that have the 'every_page' flag set to * TRUE and do not have 'preprocess' set to FALSE are aggregated together * into a single aggregate file, and that aggregate file can be reused * across a user's entire site visit, leading to faster navigation between * pages. However, stylesheets that are only needed on pages less frequently * visited, can be added by code that only runs for those particular pages, * and that code should not set the 'every_page' flag. This minimizes the * size of the aggregate file that the user needs to download when first * visiting the website. Stylesheets without the 'every_page' flag are * aggregated into a separate aggregate file. This other aggregate file is * likely to change from page to page, and each new aggregate file needs to * be downloaded when first encountered, so it should be kept relatively * small by ensuring that most commonly needed stylesheets are added to * every page. * - 'weight': The weight of the stylesheet specifies the order in which the * CSS will appear relative to other stylesheets with the same group and * 'every_page' flag. The exact ordering of stylesheets is as follows: * - First by group. * - Then by the 'every_page' flag, with TRUE coming before FALSE. * - Then by weight. * - Then by the order in which the CSS was added. For example, all else * being the same, a stylesheet added by a call to drupal_add_css() that * happened later in the page request gets added to the page after one for * which drupal_add_css() happened earlier in the page request. * - 'media': The media type for the stylesheet, e.g., all, print, screen. * Defaults to 'all'. * - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the * styles will be aggregated and compressed. Defaults to TRUE. * - 'browsers': An array containing information specifying which browsers * should load the CSS item. See drupal_pre_render_conditional_comments() * for details. * * @return * An array of queued cascading stylesheets. * * @see drupal_get_css() */ function drupal_add_css($data = NULL, $options = NULL) { $css = &drupal_static(__FUNCTION__, array()); // Construct the options, taking the defaults into consideration. if (isset($options)) { if (!is_array($options)) { $options = array('type' => $options); } } else { $options = array(); } // Create an array of CSS files for each media type first, since each type needs to be served // to the browser differently. if (isset($data)) { $options += array( 'type' => 'file', 'group' => CSS_DEFAULT, 'weight' => 0, 'every_page' => FALSE, 'media' => 'all', 'preprocess' => TRUE, 'data' => $data, 'browsers' => array(), ); $options['browsers'] += array( 'IE' => TRUE, '!IE' => TRUE, ); // Files with a query string cannot be preprocessed. if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) { $options['preprocess'] = FALSE; } // Always add a tiny value to the weight, to conserve the insertion order. $options['weight'] += count($css) / 1000; // Add the data to the CSS array depending on the type. switch ($options['type']) { case 'inline': // For inline stylesheets, we don't want to use the $data as the array // key as $data could be a very long string of CSS. $css[] = $options; break; default: // Local and external files must keep their name as the associative key // so the same CSS file is not be added twice. $css[$data] = $options; } } return $css; } /** * Returns a themed representation of all stylesheets to attach to the page. * * It loads the CSS in order, with 'module' first, then 'theme' afterwards. * This ensures proper cascading of styles so themes can easily override * module styles through CSS selectors. * * Themes may replace module-defined CSS files by adding a stylesheet with the * same filename. For example, themes/bartik/system-menus.css would replace * modules/system/system-menus.css. This allows themes to override complete * CSS files, rather than specific selectors, when necessary. * * If the original CSS file is being overridden by a theme, the theme is * responsible for supplying an accompanying RTL CSS file to replace the * module's. * * @param $css * (optional) An array of CSS files. If no array is provided, the default * stylesheets array is used instead. * @param $skip_alter * (optional) If set to TRUE, this function skips calling drupal_alter() on * $css, useful when the calling function passes a $css array that has already * been altered. * * @return * A string of XHTML CSS tags. * * @see drupal_add_css() */ function drupal_get_css($css = NULL, $skip_alter = FALSE) { if (!isset($css)) { $css = drupal_add_css(); } // Allow modules and themes to alter the CSS items. if (!$skip_alter) { drupal_alter('css', $css); } // Sort CSS items, so that they appear in the correct order. uasort($css, 'drupal_sort_css_js'); // Provide the page with information about the individual CSS files used, // information not otherwise available when CSS aggregation is enabled. The // setting is attached later in this function, but is set here, so that CSS // files removed below are still considered "used" and prevented from being // added in a later AJAX request. // Skip if no files were added to the page or jQuery.extend() will overwrite // the Drupal.settings.ajaxPageState.css object with an empty array. if (!empty($css)) { // Cast the array to an object to be on the safe side even if not empty. $setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1); } // Remove the overridden CSS files. Later CSS files override former ones. $previous_item = array(); foreach ($css as $key => $item) { if ($item['type'] == 'file') { // If defined, force a unique basename for this file. $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']); if (isset($previous_item[$basename])) { // Remove the previous item that shared the same base name. unset($css[$previous_item[$basename]]); } $previous_item[$basename] = $key; } } // Render the HTML needed to load the CSS. $styles = array( '#type' => 'styles', '#items' => $css, ); if (!empty($setting)) { $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting); } return drupal_render($styles); } /** * Sorts CSS and JavaScript resources. * * Callback for uasort() within: * - drupal_get_css() * - drupal_get_js() * * This sort order helps optimize front-end performance while providing modules * and themes with the necessary control for ordering the CSS and JavaScript * appearing on a page. * * @param $a * First item for comparison. The compared items should be associative arrays * of member items from drupal_add_css() or drupal_add_js(). * @param $b * Second item for comparison. * * @see drupal_add_css() * @see drupal_add_js() */ function drupal_sort_css_js($a, $b) { // First order by group, so that, for example, all items in the CSS_SYSTEM // group appear before items in the CSS_DEFAULT group, which appear before // all items in the CSS_THEME group. Modules may create additional groups by // defining their own constants. if ($a['group'] < $b['group']) { return -1; } elseif ($a['group'] > $b['group']) { return 1; } // Within a group, order all infrequently needed, page-specific files after // common files needed throughout the website. Separating this way allows for // the aggregate file generated for all of the common files to be reused // across a site visit without being cut by a page using a less common file. elseif ($a['every_page'] && !$b['every_page']) { return -1; } elseif (!$a['every_page'] && $b['every_page']) { return 1; } // Finally, order by weight. elseif ($a['weight'] < $b['weight']) { return -1; } elseif ($a['weight'] > $b['weight']) { return 1; } else { return 0; } } /** * Default callback to group CSS items. * * This function arranges the CSS items that are in the #items property of the * styles element into groups. Arranging the CSS items into groups serves two * purposes. When aggregation is enabled, files within a group are aggregated * into a single file, significantly improving page loading performance by * minimizing network traffic overhead. When aggregation is disabled, grouping * allows multiple files to be loaded from a single STYLE tag, enabling sites * with many modules enabled or a complex theme being used to stay within IE's * 31 CSS inclusion tag limit: http://drupal.org/node/228818. * * This function puts multiple items into the same group if they are groupable * and if they are for the same 'media' and 'browsers'. Items of the 'file' type * are groupable if their 'preprocess' flag is TRUE, items of the 'inline' type * are always groupable, and items of the 'external' type are never groupable. * This function also ensures that the process of grouping items does not change * their relative order. This requirement may result in multiple groups for the * same type, media, and browsers, if needed to accommodate other items in * between. * * @param $css * An array of CSS items, as returned by drupal_add_css(), but after * alteration performed by drupal_get_css(). * * @return * An array of CSS groups. Each group contains the same keys (e.g., 'media', * 'data', etc.) as a CSS item from the $css parameter, with the value of * each key applying to the group as a whole. Each group also contains an * 'items' key, which is the subset of items from $css that are in the group. * * @see drupal_pre_render_styles() * @see system_element_info() */ function drupal_group_css($css) { $groups = array(); // If a group can contain multiple items, we track the information that must // be the same for each item in the group, so that when we iterate the next // item, we can determine if it can be put into the current group, or if a // new group needs to be made for it. $current_group_keys = NULL; // When creating a new group, we pre-increment $i, so by initializing it to // -1, the first group will have index 0. $i = -1; foreach ($css as $item) { // The browsers for which the CSS item needs to be loaded is part of the // information that determines when a new group is needed, but the order of // keys in the array doesn't matter, and we don't want a new group if all // that's different is that order. ksort($item['browsers']); // If the item can be grouped with other items, set $group_keys to an array // of information that must be the same for all items in its group. If the // item can't be grouped with other items, set $group_keys to FALSE. We // put items into a group that can be aggregated together: whether they will // be aggregated is up to the _drupal_css_aggregate() function or an // override of that function specified in hook_css_alter(), but regardless // of the details of that function, a group represents items that can be // aggregated. Since a group may be rendered with a single HTML tag, all // items in the group must share the same information that would need to be // part of that HTML tag. switch ($item['type']) { case 'file': // Group file items if their 'preprocess' flag is TRUE. // Help ensure maximum reuse of aggregate files by only grouping // together items that share the same 'group' value and 'every_page' // flag. See drupal_add_css() for details about that. $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE; break; case 'inline': // Always group inline items. $group_keys = array($item['type'], $item['media'], $item['browsers']); break; case 'external': // Do not group external items. $group_keys = FALSE; break; } // If the group keys don't match the most recent group we're working with, // then a new group must be made. if ($group_keys !== $current_group_keys) { $i++; // Initialize the new group with the same properties as the first item // being placed into it. The item's 'data' and 'weight' properties are // unique to the item and should not be carried over to the group. $groups[$i] = $item; unset($groups[$i]['data'], $groups[$i]['weight']); $groups[$i]['items'] = array(); $current_group_keys = $group_keys ? $group_keys : NULL; } // Add the item to the current group. $groups[$i]['items'][] = $item; } return $groups; } /** * Default callback to aggregate CSS files and inline content. * * Having the browser load fewer CSS files results in much faster page loads * than when it loads many small files. This function aggregates files within * the same group into a single file unless the site-wide setting to do so is * disabled (commonly the case during site development). To optimize download, * it also compresses the aggregate files by removing comments, whitespace, and * other unnecessary content. Additionally, this functions aggregates inline * content together, regardless of the site-wide aggregation setting. * * @param $css_groups * An array of CSS groups as returned by drupal_group_css(). This function * modifies the group's 'data' property for each group that is aggregated. * * @see drupal_group_css() * @see drupal_pre_render_styles() * @see system_element_info() */ function drupal_aggregate_css(&$css_groups) { $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); // For each group that needs aggregation, aggregate its items. foreach ($css_groups as $key => $group) { switch ($group['type']) { // If a file group can be aggregated into a single file, do so, and set // the group's data property to the file path of the aggregate file. case 'file': if ($group['preprocess'] && $preprocess_css) { $css_groups[$key]['data'] = drupal_build_css_cache($group['items']); } break; // Aggregate all inline CSS content into the group's data property. case 'inline': $css_groups[$key]['data'] = ''; foreach ($group['items'] as $item) { $css_groups[$key]['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']); } break; } } } /** * #pre_render callback to add the elements needed for CSS tags to be rendered. * * For production websites, LINK tags are preferable to STYLE tags with @import * statements, because: * - They are the standard tag intended for linking to a resource. * - On Firefox 2 and perhaps other browsers, CSS files included with @import * statements don't get saved when saving the complete web page for offline * use: http://drupal.org/node/145218. * - On IE, if only LINK tags and no @import statements are used, all the CSS * files are downloaded in parallel, resulting in faster page load, but if * @import statements are used and span across multiple STYLE tags, all the * ones from one STYLE tag must be downloaded before downloading begins for * the next STYLE tag. Furthermore, IE7 does not support media declaration on * the @import statement, so multiple STYLE tags must be used when different * files are for different media types. Non-IE browsers always download in * parallel, so this is an IE-specific performance quirk: * http://www.stevesouders.com/blog/2009/04/09/dont-use-import/. * * However, IE has an annoying limit of 31 total CSS inclusion tags * (http://drupal.org/node/228818) and LINK tags are limited to one file per * tag, whereas STYLE tags can contain multiple @import statements allowing * multiple files to be loaded per tag. When CSS aggregation is disabled, a * Drupal site can easily have more than 31 CSS files that need to be loaded, so * using LINK tags exclusively would result in a site that would display * incorrectly in IE. Depending on different needs, different strategies can be * employed to decide when to use LINK tags and when to use STYLE tags. * * The strategy employed by this function is to use LINK tags for all aggregate * files and for all files that cannot be aggregated (e.g., if 'preprocess' is * set to FALSE or the type is 'external'), and to use STYLE tags for groups * of files that could be aggregated together but aren't (e.g., if the site-wide * aggregation setting is disabled). This results in all LINK tags when * aggregation is enabled, a guarantee that as many or only slightly more tags * are used with aggregation disabled than enabled (so that if the limit were to * be crossed with aggregation enabled, the site developer would also notice the * problem while aggregation is disabled), and an easy way for a developer to * view HTML source while aggregation is disabled and know what files will be * aggregated together when aggregation becomes enabled. * * This function evaluates the aggregation enabled/disabled condition on a group * by group basis by testing whether an aggregate file has been made for the * group rather than by testing the site-wide aggregation setting. This allows * this function to work correctly even if modules have implemented custom * logic for grouping and aggregating files. * * @param $element * A render array containing: * - '#items': The CSS items as returned by drupal_add_css() and altered by * drupal_get_css(). * - '#group_callback': A function to call to group #items to enable the use * of fewer tags by aggregating files and/or using multiple @import * statements within a single tag. * - '#aggregate_callback': A function to call to aggregate the items within * the groups arranged by the #group_callback function. * * @return * A render array that will render to a string of XHTML CSS tags. * * @see drupal_get_css() */ function drupal_pre_render_styles($elements) { // Group and aggregate the items. if (isset($elements['#group_callback'])) { $elements['#groups'] = $elements['#group_callback']($elements['#items']); } if (isset($elements['#aggregate_callback'])) { $elements['#aggregate_callback']($elements['#groups']); } // A dummy query-string is added to filenames, to gain control over // browser-caching. The string changes on every update or full cache // flush, forcing browsers to load a new copy of the files, as the // URL changed. $query_string = variable_get('css_js_query_string', '0'); // For inline CSS to validate as XHTML, all CSS containing XHTML needs to be // wrapped in CDATA. To make that backwards compatible with HTML 4, we need to // comment out the CDATA-tag. $embed_prefix = "\n\n"; // Defaults for LINK and STYLE elements. $link_element_defaults = array( '#type' => 'html_tag', '#tag' => 'link', '#attributes' => array( 'type' => 'text/css', 'rel' => 'stylesheet', ), ); $style_element_defaults = array( '#type' => 'html_tag', '#tag' => 'style', '#attributes' => array( 'type' => 'text/css', ), ); // Loop through each group. foreach ($elements['#groups'] as $group) { switch ($group['type']) { // For file items, there are three possibilites. // - The group has been aggregated: in this case, output a LINK tag for // the aggregate file. // - The group can be aggregated but has not been (most likely because // the site administrator disabled the site-wide setting): in this case, // output as few STYLE tags for the group as possible, using @import // statement for each file in the group. This enables us to stay within // IE's limit of 31 total CSS inclusion tags. // - The group contains items not eligible for aggregation (their // 'preprocess' flag has been set to FALSE): in this case, output a LINK // tag for each file. case 'file': // The group has been aggregated into a single file: output a LINK tag // for the aggregate file. if (isset($group['data'])) { $element = $link_element_defaults; $element['#attributes']['href'] = file_create_url($group['data']); $element['#attributes']['media'] = $group['media']; $element['#browsers'] = $group['browsers']; $elements[] = $element; } // The group can be aggregated, but hasn't been: combine multiple items // into as few STYLE tags as possible. elseif ($group['preprocess']) { $import = array(); foreach ($group['items'] as $item) { // A theme's .info file may have an entry for a file that doesn't // exist as a way of overriding a module or base theme CSS file from // being added to the page. Normally, file_exists() calls that need // to run for every page request should be minimized, but this one // is okay, because it only runs when CSS aggregation is disabled. // On a server under heavy enough load that file_exists() calls need // to be minimized, CSS aggregation should be enabled, in which case // this code is not run. When aggregation is enabled, // drupal_load_stylesheet() checks file_exists(), but only when // building the aggregate file, which is then reused for many page // requests. if (file_exists($item['data'])) { // The dummy query string needs to be added to the URL to control // browser-caching. IE7 does not support a media type on the // @import statement, so we instead specify the media for the // group on the STYLE tag. $import[] = '@import url("' . check_plain(file_create_url($item['data']) . '?' . $query_string) . '");'; } } // In addition to IE's limit of 31 total CSS inclusion tags, it also // has a limit of 31 @import statements per STYLE tag. while (!empty($import)) { $import_batch = array_slice($import, 0, 31); $import = array_slice($import, 31); $element = $style_element_defaults; $element['#value'] = implode("\n", $import_batch); $element['#attributes']['media'] = $group['media']; $element['#browsers'] = $group['browsers']; $elements[] = $element; } } // The group contains items ineligible for aggregation: output a LINK // tag for each file. else { foreach ($group['items'] as $item) { $element = $link_element_defaults; // We do not check file_exists() here, because this code runs for // files whose 'preprocess' is set to FALSE, and therefore, even // when aggregation is enabled, and we want to avoid needlessly // taxing a server that may be under heavy load. The file_exists() // performed above for files whose 'preprocess' is TRUE is done for // the benefit of theme .info files, but code that deals with files // whose 'preprocess' is FALSE is responsible for ensuring the file // exists. // The dummy query string needs to be added to the URL to control // browser-caching. $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; $element['#attributes']['href'] = file_create_url($item['data']) . $query_string_separator . $query_string; $element['#attributes']['media'] = $item['media']; $element['#browsers'] = $group['browsers']; $elements[] = $element; } } break; // For inline content, the 'data' property contains the CSS content. If // the group's 'data' property is set, then output it in a single STYLE // tag. Otherwise, output a separate STYLE tag for each item. case 'inline': if (isset($group['data'])) { $element = $style_element_defaults; $element['#value'] = $group['data']; $element['#value_prefix'] = $embed_prefix; $element['#value_suffix'] = $embed_suffix; $element['#attributes']['media'] = $group['media']; $element['#browsers'] = $group['browsers']; $elements[] = $element; } else { foreach ($group['items'] as $item) { $element = $style_element_defaults; $element['#value'] = $item['data']; $element['#value_prefix'] = $embed_prefix; $element['#value_suffix'] = $embed_suffix; $element['#attributes']['media'] = $item['media']; $element['#browsers'] = $group['browsers']; $elements[] = $element; } } break; // Output a LINK tag for each external item. The item's 'data' property // contains the full URL. case 'external': foreach ($group['items'] as $item) { $element = $link_element_defaults; $element['#attributes']['href'] = $item['data']; $element['#attributes']['media'] = $item['media']; $element['#browsers'] = $group['browsers']; $elements[] = $element; } break; } } return $elements; } /** * Aggregates and optimizes CSS files into a cache file in the files directory. * * The file name for the CSS cache file is generated from the hash of the * aggregated contents of the files in $css. This forces proxies and browsers * to download new CSS when the CSS changes. * * The cache file name is retrieved on a page load via a lookup variable that * contains an associative array. The array key is the hash of the file names * in $css while the value is the cache file name. The cache file is generated * in two cases. First, if there is no file name value for the key, which will * happen if a new file name has been added to $css or after the lookup * variable is emptied to force a rebuild of the cache. Second, the cache file * is generated if it is missing on disk. Old cache files are not deleted * immediately when the lookup variable is emptied, but are deleted after a set * period by drupal_delete_file_if_stale(). This ensures that files referenced * by a cached page will still be available. * * @param $css * An array of CSS files to aggregate and compress into one file. * * @return * The URI of the CSS cache file, or FALSE if the file could not be saved. */ function drupal_build_css_cache($css) { $data = ''; $uri = ''; $map = variable_get('drupal_css_cache_files', array()); // Create a new array so that only the file names are used to create the hash. // This prevents new aggregates from being created unnecessarily. $css_data = array(); foreach ($css as $css_file) { $css_data[] = $css_file['data']; } $key = hash('sha256', serialize($css_data)); if (isset($map[$key])) { $uri = $map[$key]; } if (empty($uri) || !file_exists($uri)) { // Build aggregate CSS file. foreach ($css as $stylesheet) { // Only 'file' stylesheets can be aggregated. if ($stylesheet['type'] == 'file') { $contents = drupal_load_stylesheet($stylesheet['data'], TRUE); // Build the base URL of this CSS file: start with the full URL. $css_base_url = file_create_url($stylesheet['data']); // Move to the parent. $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/')); // Simplify to a relative URL if the stylesheet URL starts with the // base URL of the website. if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) { $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root'])); } _drupal_build_css_path(NULL, $css_base_url . '/'); // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths. $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents); } } // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, // @import rules must proceed any other style, so we move those to the top. $regexp = '/@import[^;]+;/i'; preg_match_all($regexp, $data, $matches); $data = preg_replace($regexp, '', $data); $data = implode('', $matches[0]) . $data; // Prefix filename to prevent blocking by firewalls which reject files // starting with "ad*". $filename = 'css_' . drupal_hash_base64($data) . '.css'; // Create the css/ within the files folder. $csspath = 'public://css'; $uri = $csspath . '/' . $filename; // Create the CSS file. file_prepare_directory($csspath, FILE_CREATE_DIRECTORY); if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) { return FALSE; } // If CSS gzip compression is enabled, clean URLs are enabled (which means // that rewrite rules are working) and the zlib extension is available then // create a gzipped version of this file. This file is served conditionally // to browsers that accept gzip using .htaccess rules. if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { return FALSE; } } // Save the updated map. $map[$key] = $uri; variable_set('drupal_css_cache_files', $map); } return $uri; } /** * Prefixes all paths within a CSS file for drupal_build_css_cache(). */ function _drupal_build_css_path($matches, $base = NULL) { $_base = &drupal_static(__FUNCTION__); // Store base path for preg_replace_callback. if (isset($base)) { $_base = $base; } // Prefix with base and remove '../' segments where possible. $path = $_base . $matches[1]; $last = ''; while ($path != $last) { $last = $path; $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path); } return 'url(' . $path . ')'; } /** * Loads the stylesheet and resolves all @import commands. * * Loads a stylesheet and replaces @import commands with the contents of the * imported file. Use this instead of file_get_contents when processing * stylesheets. * * The returned contents are compressed removing white space and comments only * when CSS aggregation is enabled. This optimization will not apply for * color.module enabled themes with CSS aggregation turned off. * * @param $file * Name of the stylesheet to be processed. * @param $optimize * Defines if CSS contents should be compressed or not. * @param $reset_basepath * Used internally to facilitate recursive resolution of @import commands. * * @return * Contents of the stylesheet, including any resolved @import commands. */ function drupal_load_stylesheet($file, $optimize = NULL, $reset_basepath = TRUE) { // These statics are not cache variables, so we don't use drupal_static(). static $_optimize, $basepath; if ($reset_basepath) { $basepath = ''; } // Store the value of $optimize for preg_replace_callback with nested // @import loops. if (isset($optimize)) { $_optimize = $optimize; } // Stylesheets are relative one to each other. Start by adding a base path // prefix provided by the parent stylesheet (if necessary). if ($basepath && !file_uri_scheme($file)) { $file = $basepath . '/' . $file; } // Store the parent base path to restore it later. $parent_base_path = $basepath; // Set the current base path to process possible child imports. $basepath = dirname($file); // Load the CSS stylesheet. We suppress errors because themes may specify // stylesheets in their .info file that don't exist in the theme's path, // but are merely there to disable certain module CSS files. $content = ''; if ($contents = @file_get_contents($file)) { // Return the processed stylesheet. $content = drupal_load_stylesheet_content($contents, $_optimize); } // Restore the parent base path as the file and its childen are processed. $basepath = $parent_base_path; return $content; } /** * Processes the contents of a stylesheet for aggregation. * * @param $contents * The contents of the stylesheet. * @param $optimize * (optional) Boolean whether CSS contents should be minified. Defaults to * FALSE. * * @return * Contents of the stylesheet including the imported stylesheets. */ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { // Remove multiple charset declarations for standards compliance (and fixing Safari problems). $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents); if ($optimize) { // Perform some safe CSS optimizations. // Regexp to match comment blocks. $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; // Regexp to match double quoted strings. $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; // Regexp to match single quoted strings. $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; // Strip all comment blocks, but keep double/single quoted strings. $contents = preg_replace( "<($double_quot|$single_quot)|$comment>Ss", "$1", $contents ); // Remove certain whitespace. // There are different conditions for removing leading and trailing // whitespace. // @see http://php.net/manual/regexp.reference.subpatterns.php $contents = preg_replace('< # Strip leading and trailing whitespace. \s*([@{};,])\s* # Strip only leading whitespace from: # - Closing parenthesis: Retain "@media (bar) and foo". | \s+([\)]) # Strip only trailing whitespace from: # - Opening parenthesis: Retain "@media (bar) and foo". # - Colon: Retain :pseudo-selectors. | ([\(:])\s+ >xS', // Only one of the three capturing groups will match, so its reference // will contain the wanted value and the references for the // two non-matching groups will be replaced with empty strings. '$1$2$3', $contents ); // End the file with a new line. $contents = trim($contents); $contents .= "\n"; } // Replaces @import commands with the actual stylesheet content. // This happens recursively but omits external files. $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents); return $contents; } /** * Loads stylesheets recursively and returns contents with corrected paths. * * This function is used for recursive loading of stylesheets and * returns the stylesheet content with all url() paths corrected. */ function _drupal_load_stylesheet($matches) { $filename = $matches[1]; // Load the imported stylesheet and replace @import commands in there as well. $file = drupal_load_stylesheet($filename, NULL, FALSE); // Determine the file's directory. $directory = dirname($filename); // If the file is in the current directory, make sure '.' doesn't appear in // the url() path. $directory = $directory == '.' ? '' : $directory .'/'; // Alter all internal url() paths. Leave external paths alone. We don't need // to normalize absolute paths here (i.e. remove folder/... segments) because // that will be done later. return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file); } /** * Deletes old cached CSS files. */ function drupal_clear_css_cache() { variable_del('drupal_css_cache_files'); file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale')); } /** * Callback to delete files modified more than a set time ago. */ function drupal_delete_file_if_stale($uri) { // Default stale file threshold is 30 days. if (REQUEST_TIME - filemtime($uri) > variable_get('drupal_stale_file_threshold', 2592000)) { file_unmanaged_delete($uri); } } /** * Prepares a string for use as a CSS identifier (element, class, or ID name). * * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid * CSS identifiers (including element names, classes, and IDs in selectors.) * * @param $identifier * The identifier to clean. * @param $filter * An array of string replacements to use on the identifier. * * @return * The cleaned identifier. */ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) { // By default, we filter using Drupal's coding standards. $identifier = strtr($identifier, $filter); // Valid characters in a CSS identifier are: // - the hyphen (U+002D) // - a-z (U+0030 - U+0039) // - A-Z (U+0041 - U+005A) // - the underscore (U+005F) // - 0-9 (U+0061 - U+007A) // - ISO 10646 characters U+00A1 and higher // We strip out any character not in the above list. $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier); return $identifier; } /** * Prepares a string for use as a valid class name. * * Do not pass one string containing multiple classes as they will be * incorrectly concatenated with dashes, i.e. "one two" will become "one-two". * * @param $class * The class name to clean. * * @return * The cleaned class name. */ function drupal_html_class($class) { // The output of this function will never change, so this uses a normal // static instead of drupal_static(). static $classes = array(); if (!isset($classes[$class])) { $classes[$class] = drupal_clean_css_identifier(drupal_strtolower($class)); } return $classes[$class]; } /** * Prepares a string for use as a valid HTML ID and guarantees uniqueness. * * This function ensures that each passed HTML ID value only exists once on the * page. By tracking the already returned ids, this function enables forms, * blocks, and other content to be output multiple times on the same page, * without breaking (X)HTML validation. * * For already existing IDs, a counter is appended to the ID string. Therefore, * JavaScript and CSS code should not rely on any value that was generated by * this function and instead should rely on manually added CSS classes or * similarly reliable constructs. * * Two consecutive hyphens separate the counter from the original ID. To manage * uniqueness across multiple Ajax requests on the same page, Ajax requests * POST an array of all IDs currently present on the page, which are used to * prime this function's cache upon first invocation. * * To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive * hyphens in the originally passed $id are replaced with a single hyphen. * * @param $id * The ID to clean. * * @return * The cleaned ID. */ function drupal_html_id($id) { // If this is an Ajax request, then content returned by this page request will // be merged with content already on the base page. The HTML IDs must be // unique for the fully merged content. Therefore, initialize $seen_ids to // take into account IDs that are already in use on the base page. $seen_ids_init = &drupal_static(__FUNCTION__ . ':init'); if (!isset($seen_ids_init)) { // Ideally, Drupal would provide an API to persist state information about // prior page requests in the database, and we'd be able to add this // function's $seen_ids static variable to that state information in order // to have it properly initialized for this page request. However, no such // page state API exists, so instead, ajax.js adds all of the in-use HTML // IDs to the POST data of Ajax submissions. Direct use of $_POST is // normally not recommended as it could open up security risks, but because // the raw POST data is cast to a number before being returned by this // function, this usage is safe. if (empty($_POST['ajax_html_ids'])) { $seen_ids_init = array(); } else { // This function ensures uniqueness by appending a counter to the base id // requested by the calling function after the first occurrence of that // requested id. $_POST['ajax_html_ids'] contains the ids as they were // returned by this function, potentially with the appended counter, so // we parse that to reconstruct the $seen_ids array. if (isset($_POST['ajax_html_ids'][0]) && strpos($_POST['ajax_html_ids'][0], ',') === FALSE) { $ajax_html_ids = $_POST['ajax_html_ids']; } else { // jquery.form.js may send the server a comma-separated string as the // first element of an array (see http://drupal.org/node/1575060), so // we need to convert it to an array in that case. $ajax_html_ids = explode(',', $_POST['ajax_html_ids'][0]); } foreach ($ajax_html_ids as $seen_id) { // We rely on '--' being used solely for separating a base id from the // counter, which this function ensures when returning an id. $parts = explode('--', $seen_id, 2); if (!empty($parts[1]) && is_numeric($parts[1])) { list($seen_id, $i) = $parts; } else { $i = 1; } if (!isset($seen_ids_init[$seen_id]) || ($i > $seen_ids_init[$seen_id])) { $seen_ids_init[$seen_id] = $i; } } } } $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init); $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"), // colons (":"), and periods ("."). We strip out any character not in that // list. Note that the CSS spec doesn't allow colons or periods in identifiers // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two // characters as well. $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id); // Removing multiple consecutive hyphens. $id = preg_replace('/\-+/', '-', $id); // Ensure IDs are unique by appending a counter after the first occurrence. // The counter needs to be appended with a delimiter that does not exist in // the base ID. Requiring a unique delimiter helps ensure that we really do // return unique IDs and also helps us re-create the $seen_ids array during // Ajax requests. if (isset($seen_ids[$id])) { $id = $id . '--' . ++$seen_ids[$id]; } else { $seen_ids[$id] = 1; } return $id; } /** * Provides a standard HTML class name that identifies a page region. * * It is recommended that template preprocess functions apply this class to any * page region that is output by the theme (Drupal core already handles this in * the standard template preprocess implementation). Standardizing the class * names in this way allows modules to implement certain features, such as * drag-and-drop or dynamic Ajax loading, in a theme-independent way. * * @param $region * The name of the page region (for example, 'page_top' or 'content'). * * @return * An HTML class that identifies the region (for example, 'region-page-top' * or 'region-content'). * * @see template_preprocess_region() */ function drupal_region_class($region) { return drupal_html_class("region-$region"); } /** * Adds a JavaScript file, setting, or inline code to the page. * * The behavior of this function depends on the parameters it is called with. * Generally, it handles the addition of JavaScript to the page, either as * reference to an existing file or as inline code. The following actions can be * performed using this function: * - Add a file ('file'): Adds a reference to a JavaScript file to the page. * - Add inline JavaScript code ('inline'): Executes a piece of JavaScript code * on the current page by placing the code directly in the page (for example, * to tell the user that a new message arrived, by opening a pop up, alert * box, etc.). This should only be used for JavaScript that cannot be executed * from a file. When adding inline code, make sure that you are not relying on * $() being the jQuery function. Wrap your code in * @code (function ($) {... })(jQuery); @endcode * or use jQuery() instead of $(). * - Add external JavaScript ('external'): Allows the inclusion of external * JavaScript files that are not hosted on the local server. Note that these * external JavaScript references do not get aggregated when preprocessing is * on. * - Add settings ('setting'): Adds settings to Drupal's global storage of * JavaScript settings. Per-page settings are required by some modules to * function properly. All settings will be accessible at Drupal.settings. * * Examples: * @code * drupal_add_js('misc/collapse.js'); * drupal_add_js('misc/collapse.js', 'file'); * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline'); * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', * array('type' => 'inline', 'scope' => 'footer', 'weight' => 5) * ); * drupal_add_js('http://example.com/example.js', 'external'); * drupal_add_js(array('myModule' => array('key' => 'value')), 'setting'); * @endcode * * Calling drupal_static_reset('drupal_add_js') will clear all JavaScript added * so far. * * If JavaScript aggregation is enabled, all JavaScript files added with * $options['preprocess'] set to TRUE will be merged into one aggregate file. * Preprocessed inline JavaScript will not be aggregated into this single file. * Externally hosted JavaScripts are never aggregated. * * The reason for aggregating the files is outlined quite thoroughly here: * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due * to request overhead, one bigger file just loads faster than two smaller ones * half its size." * * $options['preprocess'] should be only set to TRUE when a file is required for * all typical visitors and most pages of a site. It is critical that all * preprocessed files are added unconditionally on every page, even if the * files are not needed on a page. This is normally done by calling * drupal_add_js() in a hook_init() implementation. * * Non-preprocessed files should only be added to the page when they are * actually needed. * * @param $data * (optional) If given, the value depends on the $options parameter, or * $options['type'] if $options is passed as an associative array: * - 'file': Path to the file relative to base_path(). * - 'inline': The JavaScript code that should be placed in the given scope. * - 'external': The absolute path to an external JavaScript file that is not * hosted on the local server. These files will not be aggregated if * JavaScript aggregation is enabled. * - 'setting': An associative array with configuration options. The array is * merged directly into Drupal.settings. All modules should wrap their * actual configuration settings in another variable to prevent conflicts in * the Drupal.settings namespace. Items added with a string key will replace * existing settings with that key; items with numeric array keys will be * added to the existing settings array. * @param $options * (optional) A string defining the type of JavaScript that is being added in * the $data parameter ('file'/'setting'/'inline'/'external'), or an * associative array. JavaScript settings should always pass the string * 'setting' only. Other types can have the following elements in the array: * - type: The type of JavaScript that is to be added to the page. Allowed * values are 'file', 'inline', 'external' or 'setting'. Defaults * to 'file'. * - scope: The location in which you want to place the script. Possible * values are 'header' or 'footer'. If your theme implements different * regions, you can also use these. Defaults to 'header'. * - group: A number identifying the group in which to add the JavaScript. * Available constants are: * - JS_LIBRARY: Any libraries, settings, or jQuery plugins. * - JS_DEFAULT: Any module-layer JavaScript. * - JS_THEME: Any theme-layer JavaScript. * The group number serves as a weight: JavaScript within a lower weight * group is presented on the page before JavaScript within a higher weight * group. * - every_page: For optimal front-end performance when aggregation is * enabled, this should be set to TRUE if the JavaScript is present on every * page of the website for users for whom it is present at all. This * defaults to FALSE. It is set to TRUE for JavaScript files that are added * via module and theme .info files. Modules that add JavaScript within * hook_init() implementations, or from other code that ensures that the * JavaScript is added to all website pages, should also set this flag to * TRUE. All JavaScript files within the same group and that have the * 'every_page' flag set to TRUE and do not have 'preprocess' set to FALSE * are aggregated together into a single aggregate file, and that aggregate * file can be reused across a user's entire site visit, leading to faster * navigation between pages. However, JavaScript that is only needed on * pages less frequently visited, can be added by code that only runs for * those particular pages, and that code should not set the 'every_page' * flag. This minimizes the size of the aggregate file that the user needs * to download when first visiting the website. JavaScript without the * 'every_page' flag is aggregated into a separate aggregate file. This * other aggregate file is likely to change from page to page, and each new * aggregate file needs to be downloaded when first encountered, so it * should be kept relatively small by ensuring that most commonly needed * JavaScript is added to every page. * - weight: A number defining the order in which the JavaScript is added to * the page relative to other JavaScript with the same 'scope', 'group', * and 'every_page' value. In some cases, the order in which the JavaScript * is presented on the page is very important. jQuery, for example, must be * added to the page before any jQuery code is run, so jquery.js uses the * JS_LIBRARY group and a weight of -20, jquery.once.js (a library drupal.js * depends on) uses the JS_LIBRARY group and a weight of -19, drupal.js uses * the JS_LIBRARY group and a weight of -1, other libraries use the * JS_LIBRARY group and a weight of 0 or higher, and all other scripts use * one of the other group constants. The exact ordering of JavaScript is as * follows: * - First by scope, with 'header' first, 'footer' last, and any other * scopes provided by a custom theme coming in between, as determined by * the theme. * - Then by group. * - Then by the 'every_page' flag, with TRUE coming before FALSE. * - Then by weight. * - Then by the order in which the JavaScript was added. For example, all * else being the same, JavaScript added by a call to drupal_add_js() that * happened later in the page request gets added to the page after one for * which drupal_add_js() happened earlier in the page request. * - defer: If set to TRUE, the defer attribute is set on the