Recherche avancée

Médias (1)

Mot : - Tags -/intégration

Autres articles (111)

  • Script d’installation automatique de MediaSPIP

    25 avril 2011, par

    Afin de palier aux difficultés d’installation dues principalement aux dépendances logicielles coté serveur, un script d’installation "tout en un" en bash a été créé afin de faciliter cette étape sur un serveur doté d’une distribution Linux compatible.
    Vous devez bénéficier d’un accès SSH à votre serveur et d’un compte "root" afin de l’utiliser, ce qui permettra d’installer les dépendances. Contactez votre hébergeur si vous ne disposez pas de cela.
    La documentation de l’utilisation du script d’installation (...)

  • Ajouter des informations spécifiques aux utilisateurs et autres modifications de comportement liées aux auteurs

    12 avril 2011, par

    La manière la plus simple d’ajouter des informations aux auteurs est d’installer le plugin Inscription3. Il permet également de modifier certains comportements liés aux utilisateurs (référez-vous à sa documentation pour plus d’informations).
    Il est également possible d’ajouter des champs aux auteurs en installant les plugins champs extras 2 et Interface pour champs extras.

  • La sauvegarde automatique de canaux SPIP

    1er avril 2010, par

    Dans le cadre de la mise en place d’une plateforme ouverte, il est important pour les hébergeurs de pouvoir disposer de sauvegardes assez régulières pour parer à tout problème éventuel.
    Pour réaliser cette tâche on se base sur deux plugins SPIP : Saveauto qui permet une sauvegarde régulière de la base de donnée sous la forme d’un dump mysql (utilisable dans phpmyadmin) mes_fichiers_2 qui permet de réaliser une archive au format zip des données importantes du site (les documents, les éléments (...)

Sur d’autres sites (8665)

  • Stream ffmpeg transcoding result to S3

    7 juin 2019, par mabead

    I want to transcode a large file using FFMPEG and store the result directly on AWS S3. This will be done inside of an AWS Lambda that has limited tmp space so I can’t store the transcoding result locally and then upload it to S3 in a second step. I won’t have enough tmp space. I therefore want to store the FFMPEG output directly on S3.

    I therefore created a S3 pre-signed url that allows ’PUT’ :

    var outputPath = s3Client.GetPreSignedURL(new Amazon.S3.Model.GetPreSignedUrlRequest
    {
       BucketName = "my-bucket",
       Expires = DateTime.UtcNow.AddMinutes(5),
       Key = "output.mp3",
       Verb = HttpVerb.PUT,
    });

    I then called ffmpeg with the resulting pre-signed url :

    ffmpeg -i C:\input.wav -y -vn -ar 44100 -ac 2 -ab 192k -f mp3 https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550427237&Signature=%2BE8Wc%2F%2FQYrvGxzc%2FgXnsvauKnac%3D

    FFMPEG returns an exit code of 1 with the following output :

    ffmpeg version N-93120-ga84af760b8 Copyright (c) 2000-2019 the FFmpeg developers
     built with gcc 8.2.1 (GCC) 20190212
     configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
     libavutil      56. 26.100 / 56. 26.100
     libavcodec     58. 47.100 / 58. 47.100
     libavformat    58. 26.101 / 58. 26.101
     libavdevice    58.  6.101 / 58.  6.101
     libavfilter     7. 48.100 /  7. 48.100
     libswscale      5.  4.100 /  5.  4.100
     libswresample   3.  4.100 /  3.  4.100
     libpostproc    55.  4.100 / 55.  4.100
    Guessed Channel Layout for Input Stream #0.0 : stereo
    Input #0, wav, from 'C:\input.wav':
     Duration: 00:04:16.72, bitrate: 3072 kb/s
       Stream #0:0: Audio: pcm_s32le ([1][0][0][0] / 0x0001), 48000 Hz, stereo, s32, 3072 kb/s
    Stream mapping:
     Stream #0:0 -> #0:0 (pcm_s32le (native) -> mp3 (libmp3lame))
    Press [q] to stop, [?] for help
    Output #0, mp3, to 'https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550427237&Signature=%2BE8Wc%2F%2FQYrvGxzc%2FgXnsvauKnac%3D':
     Metadata:
       TSSE            : Lavf58.26.101
       Stream #0:0: Audio: mp3 (libmp3lame), 44100 Hz, stereo, s32p, 192 kb/s
       Metadata:
         encoder         : Lavc58.47.100 libmp3lame
    size=     577kB time=00:00:24.58 bitrate= 192.2kbits/s speed=49.1x    
    size=    1109kB time=00:00:47.28 bitrate= 192.1kbits/s speed=47.2x    
    [tls @ 000001d73d786b00] Error in the push function.
    av_interleaved_write_frame(): I/O error
    Error writing trailer of https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550427237&Signature=%2BE8Wc%2F%2FQYrvGxzc%2FgXnsvauKnac%3D: I/O error
    size=    1143kB time=00:00:48.77 bitrate= 192.0kbits/s speed=  47x    
    video:0kB audio:1144kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
    [tls @ 000001d73d786b00] The specified session has been invalidated for some reason.
    [tls @ 000001d73d786b00] Error in the pull function.
    [https @ 000001d73d784fc0] URL read error:  -5
    Conversion failed!

    As you can see, I have a URL read error. This is a little surprising to me since I want to output to this url and not read it.

    Anybody know how I can store directly my FFMPEG output directly to S3 without having to store it locally first ?

    Edit 1
    I then tried to use the -method PUT parameter and use http instead of https to remove TLS from the equation. Here’s the output that I got when running ffmpeg with the -v trace option.

    ffmpeg version N-93120-ga84af760b8 Copyright (c) 2000-2019 the FFmpeg developers
     built with gcc 8.2.1 (GCC) 20190212
     configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
     libavutil      56. 26.100 / 56. 26.100
     libavcodec     58. 47.100 / 58. 47.100
     libavformat    58. 26.101 / 58. 26.101
     libavdevice    58.  6.101 / 58.  6.101
     libavfilter     7. 48.100 /  7. 48.100
     libswscale      5.  4.100 /  5.  4.100
     libswresample   3.  4.100 /  3.  4.100
     libpostproc    55.  4.100 / 55.  4.100
    Splitting the commandline.
    Reading option '-i' ... matched as input url with argument 'C:\input.wav'.
    Reading option '-y' ... matched as option 'y' (overwrite output files) with argument '1'.
    Reading option '-vn' ... matched as option 'vn' (disable video) with argument '1'.
    Reading option '-ar' ... matched as option 'ar' (set audio sampling rate (in Hz)) with argument '44100'.
    Reading option '-ac' ... matched as option 'ac' (set number of audio channels) with argument '2'.
    Reading option '-ab' ... matched as option 'ab' (audio bitrate (please use -b:a)) with argument '192k'.
    Reading option '-f' ... matched as option 'f' (force format) with argument 'mp3'.
    Reading option '-method' ... matched as AVOption 'method' with argument 'PUT'.
    Reading option '-v' ... matched as option 'v' (set logging level) with argument 'trace'.
    Reading option 'https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550695990&Signature=dy3RVqDlX%2BlJ0INlDkl0Lm1Rqb4%3D' ... matched as output url.
    Finished splitting the commandline.
    Parsing a group of options: global .
    Applying option y (overwrite output files) with argument 1.
    Applying option v (set logging level) with argument trace.
    Successfully parsed a group of options.
    Parsing a group of options: input url C:\input.wav.
    Successfully parsed a group of options.
    Opening an input file: C:\input.wav.
    [NULL @ 000001fb37abb180] Opening 'C:\input.wav' for reading
    [file @ 000001fb37abc180] Setting default whitelist 'file,crypto'
    Probing wav score:99 size:2048
    [wav @ 000001fb37abb180] Format wav probed with size=2048 and score=99
    [wav @ 000001fb37abb180] Before avformat_find_stream_info() pos: 54 bytes read:65590 seeks:1 nb_streams:1
    [wav @ 000001fb37abb180] parser not found for codec pcm_s32le, packets or times may be invalid.
       Last message repeated 1 times
    [wav @ 000001fb37abb180] All info found
    [wav @ 000001fb37abb180] stream 0: start_time: -192153584101141.156 duration: 256.716
    [wav @ 000001fb37abb180] format: start_time: -9223372036854.775 duration: 256.716 bitrate=3072 kb/s
    [wav @ 000001fb37abb180] After avformat_find_stream_info() pos: 204854 bytes read:294966 seeks:1 frames:50
    Guessed Channel Layout for Input Stream #0.0 : stereo
    Input #0, wav, from 'C:\input.wav':
     Duration: 00:04:16.72, bitrate: 3072 kb/s
       Stream #0:0, 50, 1/48000: Audio: pcm_s32le ([1][0][0][0] / 0x0001), 48000 Hz, stereo, s32, 3072 kb/s
    Successfully opened the file.
    Parsing a group of options: output url https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550695990&Signature=dy3RVqDlX%2BlJ0INlDkl0Lm1Rqb4%3D.
    Applying option vn (disable video) with argument 1.
    Applying option ar (set audio sampling rate (in Hz)) with argument 44100.
    Applying option ac (set number of audio channels) with argument 2.
    Applying option ab (audio bitrate (please use -b:a)) with argument 192k.
    Applying option f (force format) with argument mp3.
    Successfully parsed a group of options.
    Opening an output file: https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550695990&Signature=dy3RVqDlX%2BlJ0INlDkl0Lm1Rqb4%3D.
    [http @ 000001fb37b15140] Setting default whitelist 'http,https,tls,rtp,tcp,udp,crypto,httpproxy'
    [tcp @ 000001fb37b16c80] Original list of addresses:
    [tcp @ 000001fb37b16c80] Address 52.216.8.203 port 80
    [tcp @ 000001fb37b16c80] Interleaved list of addresses:
    [tcp @ 000001fb37b16c80] Address 52.216.8.203 port 80
    [tcp @ 000001fb37b16c80] Starting connection attempt to 52.216.8.203 port 80
    [tcp @ 000001fb37b16c80] Successfully connected to 52.216.8.203 port 80
    [http @ 000001fb37b15140] request: PUT /output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550695990&Signature=dy3RVqDlX%2BlJ0INlDkl0Lm1Rqb4%3D HTTP/1.1
    Transfer-Encoding: chunked
    User-Agent: Lavf/58.26.101
    Accept: */*
    Connection: close
    Host: landr-distribution-reportsdev-mb.s3.amazonaws.com
    Icy-MetaData: 1
    Successfully opened the file.
    Stream mapping:
     Stream #0:0 -> #0:0 (pcm_s32le (native) -> mp3 (libmp3lame))
    Press [q] to stop, [?] for help
    cur_dts is invalid (this is harmless if it occurs once at the start per stream)
    detected 8 logical cores
    [graph_0_in_0_0 @ 000001fb37b21080] Setting 'time_base' to value '1/48000'
    [graph_0_in_0_0 @ 000001fb37b21080] Setting 'sample_rate' to value '48000'
    [graph_0_in_0_0 @ 000001fb37b21080] Setting 'sample_fmt' to value 's32'
    [graph_0_in_0_0 @ 000001fb37b21080] Setting 'channel_layout' to value '0x3'
    [graph_0_in_0_0 @ 000001fb37b21080] tb:1/48000 samplefmt:s32 samplerate:48000 chlayout:0x3
    [format_out_0_0 @ 000001fb37b22cc0] Setting 'sample_fmts' to value 's32p|fltp|s16p'
    [format_out_0_0 @ 000001fb37b22cc0] Setting 'sample_rates' to value '44100'
    [format_out_0_0 @ 000001fb37b22cc0] Setting 'channel_layouts' to value '0x3'
    [format_out_0_0 @ 000001fb37b22cc0] auto-inserting filter 'auto_resampler_0' between the filter 'Parsed_anull_0' and the filter 'format_out_0_0'
    [AVFilterGraph @ 000001fb37b0d940] query_formats: 4 queried, 6 merged, 3 already done, 0 delayed
    [auto_resampler_0 @ 000001fb37b251c0] picking s32p out of 3 ref:s32
    [auto_resampler_0 @ 000001fb37b251c0] [SWR @ 000001fb37b252c0] Using fltp internally between filters
    [auto_resampler_0 @ 000001fb37b251c0] ch:2 chl:stereo fmt:s32 r:48000Hz -> ch:2 chl:stereo fmt:s32p r:44100Hz
    Output #0, mp3, to 'https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550695990&Signature=dy3RVqDlX%2BlJ0INlDkl0Lm1Rqb4%3D':
     Metadata:
       TSSE            : Lavf58.26.101
       Stream #0:0, 0, 1/44100: Audio: mp3 (libmp3lame), 44100 Hz, stereo, s32p, delay 1105, 192 kb/s
       Metadata:
         encoder         : Lavc58.47.100 libmp3lame
    cur_dts is invalid (this is harmless if it occurs once at the start per stream)
       Last message repeated 6 times
    size=     649kB time=00:00:27.66 bitrate= 192.2kbits/s speed=55.3x    
    size=    1207kB time=00:00:51.48 bitrate= 192.1kbits/s speed=51.5x    
    av_interleaved_write_frame(): Unknown error
    No more output streams to write to, finishing.
    [libmp3lame @ 000001fb37b147c0] Trying to remove 47 more samples than there are in the queue
    Error writing trailer of https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550695990&Signature=dy3RVqDlX%2BlJ0INlDkl0Lm1Rqb4%3D: Error number -10054 occurred
    size=    1251kB time=00:00:53.39 bitrate= 192.0kbits/s speed=51.5x    
    video:0kB audio:1252kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
    Input file #0 (C:\input.wav):
     Input stream #0:0 (audio): 5014 packets read (20537344 bytes); 5014 frames decoded (2567168 samples);
     Total: 5014 packets (20537344 bytes) demuxed
    Output file #0 (https://my-bucket.s3.amazonaws.com/output.mp3?AWSAccessKeyId=AKIAJDSGJWM63VQEXHIQ&Expires=1550695990&Signature=dy3RVqDlX%2BlJ0INlDkl0Lm1Rqb4%3D):
     Output stream #0:0 (audio): 2047 frames encoded (2358144 samples); 2045 packets muxed (1282089 bytes);
     Total: 2045 packets (1282089 bytes) muxed
    5014 frames successfully decoded, 0 decoding errors
    [AVIOContext @ 000001fb37b1f440] Statistics: 0 seeks, 2046 writeouts
    [http @ 000001fb37b15140] URL read error:  -10054
    [AVIOContext @ 000001fb37ac4400] Statistics: 20611126 bytes read, 1 seeks
    Conversion failed!

    So it looks like it is able to connect to my S3 pre-signed url but I still have the Error writing trailer error coupled with a URL read error.

  • Matomo analytics for wordpress

    15 octobre 2019, par Matomo Core Team — Community

    Self-hosting web analytics got a whole lot easier ! Introducing Matomo for WordPress

    Be the first to try it out ! Your feedback is much needed and appreciated

    Get a fully functioning Matomo (which is comparable to Google Analytics) in seconds ! How ? With the new Matomo Analytics for WordPress plugin. 

    Web analytics in WordPress has never been easier to get, or more powerful. Matomo Analytics for WordPress is the one-stop problem solver. It’ll save you time, money and give you the insights to help your website or business succeed. 

    Best of all, we get to further the goal of decentralising the internet. Our hope is for Matomo Analytics for WordPress to spread far and wide. We’re so excited that more and more people can now get their hands on this powerful, free, open-source analytics platform, in a few clicks !

    Download now and check it out !

    What do you get ?

    • No more signing up to third party analytics service (like Google)
    • No more sending away your valuable data to a third party service (like Google)
    • Easy setup – install with a few clicks, no tracking code installation or developer knowledge needed
    • 100% accurate data – no data sampling and no data limits 
    • Full data ownership – all data is stored on your servers and no one else can see your data
    • Privacy protection / GDPR compliance
    • Ecommerce tracking out-of-the-box (Woocommerce, Easy Digital Downloads, and MemberPress) and we’re keen to add many more over time
    • Powerful features – segmenting, comparing reports, different visualisations, real-time reports, visit logs and visitor profiles, Matomo Tag Manager, dashboards, data export, APIs, and many more
    • Compared to other WordPress solutions we don’t charge you extra for basic features that should work out-of-the-box
    • Just like Matomo On-Premise, Matomo Analytics for WordPress is free

    We need your feedback !

    We all know and love the versatility of WordPress – with over 55,000 plugins and all the different ways of hosting it. However, with this great versatility comes the potential for things to be missed, so we’re keen to hear your feedback.

    Thank you ! We really appreciate your help on this ❤️

    How do you get Matomo Analytics for WordPress ?

    You can then upload it in your WordPress by going to “Plugins => Add New”. During the upload, if you get an error like “Are you sure you want to do this ?”, we recommend you upload the extracted zip file on to your server and into your ‘wp-content/plugins’ folder manually using ftp or ssh. Make sure the plugin name is ‘matomo’.

    All you need is at least WordPress 4.8 and PHP 7.0 or later. MySQL 5.1+ is recommended. 

    The source code is available at : https://github.com/matomo-org/wp-matomo/

    In perfect harmony : Matomo and WordPress

    Matomo Analytics for WordPress

    The idea for this started two years ago when we realised the similarities between the Matomo and WordPress project. 

    Not only from a technological point of view – where both are based on PHP and MySQL and can be extended using plugins – but also from a philosophical, license and values point of view. We both believe in privacy, security, data ownership, openness, transparency, having things working out-of-the-box, simplicity etc. 

    WordPress is currently used on approximately 30% of all websites. Many of them use the self-hosted open-source WordPress version. Giving everyone in this market the opportunity to easily get a powerful web analytics platform for free, means a lot to us. We believe WordPress users get a real choice besides the standard solution of Google Analytics, and it furthers our effort and goal of decentralising the internet. 

    We’re hoping more people will be empowered to protect user privacy, have access to a great free and open-source tool, and keep control of data in their own hands.

    We hope you feel the same. Help us spread the word to your friends and get them in on this awesome new project !

    Share on facebook
    Share on twitter
    Share on linkedin

    FAQs

    Isn’t there already a WP-Matomo plugin for WordPress available ?

    Yes, the existing WP-Matomo (WP-Piwik) plugin is an awesome plugin to connect your existing Matomo On-Premise or Matomo Cloud account with WordPress. The difference is that this new plugin installs Matomo Analytics fully in your WordPress. So you get the convenience of having a powerful analytics platform within your WordPress.

    We highly recommend you install this new plugin if you use WordPress and are not running Matomo yet. 

    If you are already using Matomo on our Cloud or On-Premise, we’d still highly recommend you use WP-Matomo (WP-Piwik). So that you get an easier way of inserting the tracking code into your WordPress site and get insights faster.

    I have a high traffic website, will it be an issue ?

    If you have a lot of traffic, we’d advise you to install Matomo On-Premise separately. There’s no specific traffic threshold we can give you on when it’s better to use Matomo On-Premise. It really depends on your server. 

    We reckon if you have more than 500,000 page views a month, you may want to think about using Matomo On-Premise with WP-Matomo instead, but this is just an estimate. In general, if the load on your server is already quite high, then it might be better to install Matomo on a separate server. See also recommended server sizing for running Matomo.

    How do I report a bug or request a new feature in Matomo for WordPress ?

    Please create an issue, on our repository whenever you find a bug or if you have any suggestion or ideas of improvement. We want to build an outstanding analytics experience for WordPress !

    Have another question you’re dying to ask ? The Matomo for WordPress FAQ page might have the answer you need. 

    Matomo Analytics for WordPress newsletter

    Get ahead of the crowd – signup to our exclusive Matomo for WordPress newsletter to get the latest updates on this exciting new project.

    &lt;script type=&quot;text/javascript&quot;&gt;<br />
    (function(global) {<br />
     function serialize(form){if(!form||form.nodeName!==&quot;FORM&quot;){return }var i,j,q=[];for(i=form.elements.length-1;i&gt;=0;i=i-1){if(form.elements[i].name===&quot;&quot;){continue}switch(form.elements[i].nodeName){case&quot;INPUT&quot;:switch(form.elements[i].type){case&quot;text&quot;:case&quot;hidden&quot;:case&quot;password&quot;:case&quot;button&quot;:case&quot;reset&quot;:case&quot;submit&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break;case&quot;checkbox&quot;:case&quot;radio&quot;:if(form.elements[i].checked){q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value))}break;case&quot;file&quot;:break}break;case&quot;TEXTAREA&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break;case&quot;SELECT&quot;:switch(form.elements[i].type){case&quot;select-one&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break;case&quot;select-multiple&quot;:for(j=form.elements[i].options.length-1;j&gt;=0;j=j-1){if(form.elements[i].options[j].selected){q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].options[j].value))}}break}break;case&quot;BUTTON&quot;:switch(form.elements[i].type){case&quot;reset&quot;:case&quot;submit&quot;:case&quot;button&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break}break}}return q.join(&quot;&amp;&quot;)};<br />
    <br />
    <br />
     function extend(destination, source) {<br />
       for (var prop in source) {<br />
         destination[prop] = source[prop];<br />
       }<br />
     }<br />
    <br />
     if (!Mimi) var Mimi = {};<br />
     if (!Mimi.Signups) Mimi.Signups = {};<br />
    <br />
     Mimi.Signups.EmbedValidation = function() {<br />
       this.initialize();<br />
    <br />
       var _this = this;<br />
       if (document.addEventListener) {<br />
         this.form.addEventListener('submit', function(e){<br />
           _this.onFormSubmit(e);<br />
         });<br />
       } else {<br />
         this.form.attachEvent('onsubmit', function(e){<br />
           _this.onFormSubmit(e);<br />
         });<br />
       }<br />
     };<br />
    <br />
     extend(Mimi.Signups.EmbedValidation.prototype, {<br />
       initialize: function() {<br />
         this.form         = document.getElementById('ema_signup_form');<br />
         this.submit       = document.getElementById('webform_submit_button');<br />
         this.callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());<br />
         this.validEmail   = /.+@.+\..+/<br />
       },<br />
    <br />
       onFormSubmit: function(e) {<br />
         e.preventDefault();<br />
    <br />
         this.validate();<br />
         if (this.isValid) {<br />
           this.submitForm();<br />
         } else {<br />
           this.revalidateOnChange();<br />
         }<br />
       },<br />
    <br />
       validate: function() {<br />
         this.isValid = true;<br />
         this.emailValidation();<br />
         this.fieldAndListValidation();<br />
         this.updateFormAfterValidation();<br />
       },<br />
    <br />
       emailValidation: function() {<br />
         var email = document.getElementById('signup_email');<br />
    <br />
         if (this.validEmail.test(email.value)) {<br />
           this.removeTextFieldError(email);<br />
         } else {<br />
           this.textFieldError(email);<br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       fieldAndListValidation: function() {<br />
         var fields = this.form.querySelectorAll('.mimi_field.required');<br />
    <br />
         for (var i = 0; i &lt; fields.length; ++i) {<br />
           var field = fields[i],<br />
               type  = this.fieldType(field);<br />
           if (type === 'checkboxes' || type === 'radio_buttons' || type === 'age_check') {<br />
             this.checkboxAndRadioValidation(field);<br />
           } else {<br />
             this.textAndDropdownValidation(field, type);<br />
           }<br />
         }<br />
       },<br />
    <br />
       fieldType: function(field) {<br />
         var type = field.querySelectorAll('.field_type');<br />
    <br />
         if (type.length) {<br />
           return type[0].getAttribute('data-field-type');<br />
         } else if (field.className.indexOf('checkgroup') &gt;= 0) {<br />
           return 'checkboxes';<br />
         } else {<br />
           return 'text_field';<br />
         }<br />
       },<br />
    <br />
       checkboxAndRadioValidation: function(field) {<br />
         var inputs   = field.getElementsByTagName('input'),<br />
             selected = false;<br />
    <br />
         for (var i = 0; i &lt; inputs.length; ++i) {<br />
           var input = inputs[i];<br />
           if((input.type === 'checkbox' || input.type === 'radio') &amp;&amp; input.checked) {<br />
             selected = true;<br />
           }<br />
         }<br />
    <br />
         if (selected) {<br />
           field.className = field.className.replace(/ invalid/g, '');<br />
         } else {<br />
           if (field.className.indexOf('invalid') === -1) {<br />
             field.className += ' invalid';<br />
           }<br />
    <br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       textAndDropdownValidation: function(field, type) {<br />
         var inputs = field.getElementsByTagName('input');<br />
    <br />
         for (var i = 0; i &lt; inputs.length; ++i) {<br />
           var input = inputs[i];<br />
           if (input.name.indexOf('signup') &gt;= 0) {<br />
             if (type === 'text_field') {<br />
               this.textValidation(input);<br />
             } else {<br />
               this.dropdownValidation(field, input);<br />
             }<br />
           }<br />
         }<br />
         this.htmlEmbedDropdownValidation(field);<br />
       },<br />
    <br />
       textValidation: function(input) {<br />
         if (input.id === 'signup_email') return;<br />
    <br />
         if (input.value) {<br />
           this.removeTextFieldError(input);<br />
         } else {<br />
           this.textFieldError(input);<br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       dropdownValidation: function(field, input) {<br />
         if (input.value) {<br />
           field.className = field.className.replace(/ invalid/g, '');<br />
         } else {<br />
           if (field.className.indexOf('invalid') === -1) field.className += ' invalid';<br />
           this.onSelectCallback(input);<br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       htmlEmbedDropdownValidation: function(field) {<br />
         var dropdowns = field.querySelectorAll('.mimi_html_dropdown');<br />
         var _this = this;<br />
    <br />
         for (var i = 0; i &lt; dropdowns.length; ++i) {<br />
           var dropdown = dropdowns[i];<br />
    <br />
           if (dropdown.value) {<br />
             field.className = field.className.replace(/ invalid/g, '');<br />
           } else {<br />
             if (field.className.indexOf('invalid') === -1) field.className += ' invalid';<br />
             this.isValid = false;<br />
             dropdown.onchange = (function(){ _this.validate(); });<br />
           }<br />
         }<br />
       },<br />
    <br />
       textFieldError: function(input) {<br />
         input.className   = 'required invalid';<br />
         input.placeholder = input.getAttribute('data-required-field');<br />
       },<br />
    <br />
       removeTextFieldError: function(input) {<br />
         input.className   = 'required';<br />
         input.placeholder = '';<br />
       },<br />
    <br />
       onSelectCallback: function(input) {<br />
         if (typeof Widget === 'undefined' || !Widget.BasicDropdown) return;<br />
    <br />
         var dropdownEl = input.parentNode,<br />
             instances  = Widget.BasicDropdown.instances,<br />
             _this = this;<br />
    <br />
         for (var i = 0; i &lt; instances.length; ++i) {<br />
           var instance = instances[i];<br />
           if (instance.wrapperEl === dropdownEl) {<br />
             instance.onSelect = function(){ _this.validate() };<br />
           }<br />
         }<br />
       },<br />
    <br />
       updateFormAfterValidation: function() {<br />
         this.form.className   = this.setFormClassName();<br />
         this.submit.value     = this.submitButtonText();<br />
         this.submit.disabled  = !this.isValid;<br />
         this.submit.className = this.isValid ? 'submit' : 'disabled';<br />
       },<br />
    <br />
       setFormClassName: function() {<br />
         var name = this.form.className;<br />
    <br />
         if (this.isValid) {<br />
           return name.replace(/\s?mimi_invalid/, '');<br />
         } else {<br />
           if (name.indexOf('mimi_invalid') === -1) {<br />
             return name += ' mimi_invalid';<br />
           } else {<br />
             return name;<br />
           }<br />
         }<br />
       },<br />
    <br />
       submitButtonText: function() {<br />
         var invalidFields = document.querySelectorAll('.invalid'),<br />
             text;<br />
    <br />
         if (this.isValid || !invalidFields) {<br />
           text = this.submit.getAttribute('data-default-text');<br />
         } else {<br />
           if (invalidFields.length || invalidFields[0].className.indexOf('checkgroup') === -1) {<br />
             text = this.submit.getAttribute('data-invalid-text');<br />
           } else {<br />
             text = this.submit.getAttribute('data-choose-list');<br />
           }<br />
         }<br />
         return text;<br />
       },<br />
    <br />
       submitForm: function() {<br />
         this.formSubmitting();<br />
    <br />
         var _this = this;<br />
         window[this.callbackName] = function(response) {<br />
           delete window[this.callbackName];<br />
           document.body.removeChild(script);<br />
           _this.onSubmitCallback(response);<br />
         };<br />
    <br />
         var script = document.createElement('script');<br />
         script.src = this.formUrl('json');<br />
         document.body.appendChild(script);<br />
       },<br />
    <br />
       formUrl: function(format) {<br />
         var action  = this.form.action;<br />
         if (format === 'json') action += '.json';<br />
         return action + '?callback=' + this.callbackName + '&amp;' + serialize(this.form);<br />
       },<br />
    <br />
       formSubmitting: function() {<br />
         this.form.className  += ' mimi_submitting';<br />
         this.submit.value     = this.submit.getAttribute('data-submitting-text');<br />
         this.submit.disabled  = true;<br />
         this.submit.className = 'disabled';<br />
       },<br />
    <br />
       onSubmitCallback: function(response) {<br />
         if (response.success) {<br />
           this.onSubmitSuccess(response.result);<br />
         } else {<br />
           top.location.href = this.formUrl('html');<br />
         }<br />
       },<br />
    <br />
       onSubmitSuccess: function(result) {<br />
         if (result.has_redirect) {<br />
           top.location.href = result.redirect;<br />
         } else if(result.single_opt_in || !result.confirmation_html) {<br />
           this.disableForm();<br />
           this.updateSubmitButtonText(this.submit.getAttribute('data-thanks'));<br />
         } else {<br />
           this.showConfirmationText(result.confirmation_html);<br />
         }<br />
       },<br />
    <br />
       showConfirmationText: function(html) {<br />
         var fields = this.form.querySelectorAll('.mimi_field');<br />
    <br />
         for (var i = 0; i &lt; fields.length; ++i) {<br />
           fields[i].style['display'] = 'none';<br />
         }<br />
    <br />
         (this.form.querySelectorAll('fieldset')[0] || this.form).innerHTML = html;<br />
       },<br />
    <br />
       disableForm: function() {<br />
         var elements = this.form.elements;<br />
         for (var i = 0; i &lt; elements.length; ++i) {<br />
           elements[i].disabled = true;<br />
         }<br />
       },<br />
    <br />
       updateSubmitButtonText: function(text) {<br />
         this.submit.value = text;<br />
       },<br />
    <br />
       revalidateOnChange: function() {<br />
         var fields = this.form.querySelectorAll(&quot;.mimi_field.required&quot;),<br />
             _this = this;<br />
    <br />
         var onTextFieldChange = function() {<br />
           if (this.getAttribute('name') === 'signup[email]') {<br />
             if (_this.validEmail.test(this.value)) _this.validate();<br />
           } else {<br />
             if (this.value.length === 1) _this.validate();<br />
           }<br />
         }<br />
    <br />
         for (var i = 0; i &lt; fields.length; ++i) {<br />
           var inputs = fields[i].getElementsByTagName('input');<br />
           for (var j = 0; j &lt; inputs.length; ++j) {<br />
             if (this.fieldType(fields[i]) === 'text_field') {<br />
               inputs[j].onkeyup = onTextFieldChange;<br />
               inputs[j].onchange = onTextFieldChange; <br />
             } else {<br />
               inputs[j].onchange = function(){ _this.validate() };<br />
             }<br />
           }<br />
         }<br />
       }<br />
     });<br />
    <br />
     if (document.addEventListener) {<br />
       document.addEventListener(&quot;DOMContentLoaded&quot;, function() {<br />
         new Mimi.Signups.EmbedValidation();<br />
       });<br />
     }<br />
     else {<br />
       window.attachEvent('onload', function() {<br />
         new Mimi.Signups.EmbedValidation();<br />
       });<br />
     }<br />
    })(this);<br />
    &lt;/script&gt;
  • Matomo analytics for wordpress

    15 octobre 2019, par Matomo Core Team — Community

    Self-hosting web analytics got a whole lot easier ! Introducing Matomo for WordPress

    Be the first to try it out ! Your feedback is much needed and appreciated

    Get a fully functioning Matomo (which is comparable to Google Analytics) in seconds ! How ? With the new Matomo Analytics for WordPress plugin. 

    Web analytics in WordPress has never been easier to get, or more powerful. Matomo Analytics for WordPress is the one-stop problem solver. It’ll save you time, money and give you the insights to help your website or business succeed. 

    Best of all, we get to further the goal of decentralising the internet. Our hope is for Matomo Analytics for WordPress to spread far and wide. We’re so excited that more and more people can now get their hands on this powerful, free, open-source analytics platform, in a few clicks !

    Download now and check it out !

    What do you get ?

    • No more signing up to third party analytics service (like Google)
    • No more sending away your valuable data to a third party service (like Google)
    • Easy setup – install with a few clicks, no tracking code installation or developer knowledge needed
    • 100% accurate data – no data sampling and no data limits 
    • Full data ownership – all data is stored on your servers and no one else can see your data
    • Privacy protection / GDPR compliance
    • Ecommerce tracking out-of-the-box (Woocommerce, Easy Digital Downloads, and MemberPress) and we’re keen to add many more over time
    • Powerful features – segmenting, comparing reports, different visualisations, real-time reports, visit logs and visitor profiles, Matomo Tag Manager, dashboards, data export, APIs, and many more
    • Compared to other WordPress solutions we don’t charge you extra for basic features that should work out-of-the-box
    • Just like Matomo On-Premise, Matomo Analytics for WordPress is free

    We need your feedback !

    We all know and love the versatility of WordPress – with over 55,000 plugins and all the different ways of hosting it. However, with this great versatility comes the potential for things to be missed, so we’re keen to hear your feedback.

    Thank you ! We really appreciate your help on this ❤️

    How do you get Matomo Analytics for WordPress ?

    Log in to your WordPress and go to “Plugins => Add New”, search for “Matomo Analytics – Ethical Stats. Powerful Insights”, click on “Install” and then “Activate”.

    All you need is at least WordPress 4.8 and PHP 7.0 or later. MySQL 5.1+ is recommended. 

    The source code is available at : https://github.com/matomo-org/wp-matomo/

    In perfect harmony : Matomo and WordPress

    Matomo Analytics for WordPress

    The idea for this started two years ago when we realised the similarities between the Matomo and WordPress project. 

    Not only from a technological point of view – where both are based on PHP and MySQL and can be extended using plugins – but also from a philosophical, license and values point of view. We both believe in privacy, security, data ownership, openness, transparency, having things working out-of-the-box, simplicity etc. 

    WordPress is currently used on approximately 30% of all websites. Many of them use the self-hosted open-source WordPress version. Giving everyone in this market the opportunity to easily get a powerful web analytics platform for free, means a lot to us. We believe WordPress users get a real choice besides the standard solution of Google Analytics, and it furthers our effort and goal of decentralising the internet. 

    We’re hoping more people will be empowered to protect user privacy, have access to a great free and open-source tool, and keep control of data in their own hands.

    We hope you feel the same. Help us spread the word to your friends and get them in on this awesome new project !

    Share on facebook
    Share on twitter
    Share on linkedin

    FAQs

    Isn’t there already a WP-Matomo plugin for WordPress available ?

    Yes, the existing WP-Matomo (WP-Piwik) plugin is an awesome plugin to connect your existing Matomo On-Premise or Matomo Cloud account with WordPress. The difference is that this new plugin installs Matomo Analytics fully in your WordPress. So you get the convenience of having a powerful analytics platform within your WordPress.

    We highly recommend you install this new plugin if you use WordPress and are not running Matomo yet. 

    If you are already using Matomo on our Cloud or On-Premise, we’d still highly recommend you use WP-Matomo (WP-Piwik). So that you get an easier way of inserting the tracking code into your WordPress site and get insights faster.

    I have a high traffic website, will it be an issue ?

    If you have a lot of traffic, we’d advise you to install Matomo On-Premise separately. There’s no specific traffic threshold we can give you on when it’s better to use Matomo On-Premise. It really depends on your server. 

    We reckon if you have more than 500,000 page views a month, you may want to think about using Matomo On-Premise with WP-Matomo instead, but this is just an estimate. In general, if the load on your server is already quite high, then it might be better to install Matomo on a separate server. See also recommended server sizing for running Matomo.

    How do I report a bug or request a new feature in Matomo for WordPress ?

    Please create an issue, on our repository whenever you find a bug or if you have any suggestion or ideas of improvement. We want to build an outstanding analytics experience for WordPress !

    Have another question you’re dying to ask ? The Matomo for WordPress FAQ page might have the answer you need. 

    Matomo Analytics for WordPress newsletter

    Get ahead of the crowd – signup to our exclusive Matomo for WordPress newsletter to get the latest updates on this exciting new project.

    &lt;script type=&quot;text/javascript&quot;&gt;<br />
    (function(global) {<br />
     function serialize(form){if(!form||form.nodeName!==&quot;FORM&quot;){return }var i,j,q=[];for(i=form.elements.length-1;i&gt;=0;i=i-1){if(form.elements[i].name===&quot;&quot;){continue}switch(form.elements[i].nodeName){case&quot;INPUT&quot;:switch(form.elements[i].type){case&quot;text&quot;:case&quot;hidden&quot;:case&quot;password&quot;:case&quot;button&quot;:case&quot;reset&quot;:case&quot;submit&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break;case&quot;checkbox&quot;:case&quot;radio&quot;:if(form.elements[i].checked){q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value))}break;case&quot;file&quot;:break}break;case&quot;TEXTAREA&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break;case&quot;SELECT&quot;:switch(form.elements[i].type){case&quot;select-one&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break;case&quot;select-multiple&quot;:for(j=form.elements[i].options.length-1;j&gt;=0;j=j-1){if(form.elements[i].options[j].selected){q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].options[j].value))}}break}break;case&quot;BUTTON&quot;:switch(form.elements[i].type){case&quot;reset&quot;:case&quot;submit&quot;:case&quot;button&quot;:q.push(form.elements[i].name+&quot;=&quot;+encodeURIComponent(form.elements[i].value));break}break}}return q.join(&quot;&amp;&quot;)};<br />
    <br />
    <br />
     function extend(destination, source) {<br />
       for (var prop in source) {<br />
         destination[prop] = source[prop];<br />
       }<br />
     }<br />
    <br />
     if (!Mimi) var Mimi = {};<br />
     if (!Mimi.Signups) Mimi.Signups = {};<br />
    <br />
     Mimi.Signups.EmbedValidation = function() {<br />
       this.initialize();<br />
    <br />
       var _this = this;<br />
       if (document.addEventListener) {<br />
         this.form.addEventListener('submit', function(e){<br />
           _this.onFormSubmit(e);<br />
         });<br />
       } else {<br />
         this.form.attachEvent('onsubmit', function(e){<br />
           _this.onFormSubmit(e);<br />
         });<br />
       }<br />
     };<br />
    <br />
     extend(Mimi.Signups.EmbedValidation.prototype, {<br />
       initialize: function() {<br />
         this.form         = document.getElementById('ema_signup_form');<br />
         this.submit       = document.getElementById('webform_submit_button');<br />
         this.callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());<br />
         this.validEmail   = /.+@.+\..+/<br />
       },<br />
    <br />
       onFormSubmit: function(e) {<br />
         e.preventDefault();<br />
    <br />
         this.validate();<br />
         if (this.isValid) {<br />
           this.submitForm();<br />
         } else {<br />
           this.revalidateOnChange();<br />
         }<br />
       },<br />
    <br />
       validate: function() {<br />
         this.isValid = true;<br />
         this.emailValidation();<br />
         this.fieldAndListValidation();<br />
         this.updateFormAfterValidation();<br />
       },<br />
    <br />
       emailValidation: function() {<br />
         var email = document.getElementById('signup_email');<br />
    <br />
         if (this.validEmail.test(email.value)) {<br />
           this.removeTextFieldError(email);<br />
         } else {<br />
           this.textFieldError(email);<br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       fieldAndListValidation: function() {<br />
         var fields = this.form.querySelectorAll('.mimi_field.required');<br />
    <br />
         for (var i = 0; i &lt; fields.length; ++i) {<br />
           var field = fields[i],<br />
               type  = this.fieldType(field);<br />
           if (type === 'checkboxes' || type === 'radio_buttons' || type === 'age_check') {<br />
             this.checkboxAndRadioValidation(field);<br />
           } else {<br />
             this.textAndDropdownValidation(field, type);<br />
           }<br />
         }<br />
       },<br />
    <br />
       fieldType: function(field) {<br />
         var type = field.querySelectorAll('.field_type');<br />
    <br />
         if (type.length) {<br />
           return type[0].getAttribute('data-field-type');<br />
         } else if (field.className.indexOf('checkgroup') &gt;= 0) {<br />
           return 'checkboxes';<br />
         } else {<br />
           return 'text_field';<br />
         }<br />
       },<br />
    <br />
       checkboxAndRadioValidation: function(field) {<br />
         var inputs   = field.getElementsByTagName('input'),<br />
             selected = false;<br />
    <br />
         for (var i = 0; i &lt; inputs.length; ++i) {<br />
           var input = inputs[i];<br />
           if((input.type === 'checkbox' || input.type === 'radio') &amp;&amp; input.checked) {<br />
             selected = true;<br />
           }<br />
         }<br />
    <br />
         if (selected) {<br />
           field.className = field.className.replace(/ invalid/g, '');<br />
         } else {<br />
           if (field.className.indexOf('invalid') === -1) {<br />
             field.className += ' invalid';<br />
           }<br />
    <br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       textAndDropdownValidation: function(field, type) {<br />
         var inputs = field.getElementsByTagName('input');<br />
    <br />
         for (var i = 0; i &lt; inputs.length; ++i) {<br />
           var input = inputs[i];<br />
           if (input.name.indexOf('signup') &gt;= 0) {<br />
             if (type === 'text_field') {<br />
               this.textValidation(input);<br />
             } else {<br />
               this.dropdownValidation(field, input);<br />
             }<br />
           }<br />
         }<br />
         this.htmlEmbedDropdownValidation(field);<br />
       },<br />
    <br />
       textValidation: function(input) {<br />
         if (input.id === 'signup_email') return;<br />
    <br />
         if (input.value) {<br />
           this.removeTextFieldError(input);<br />
         } else {<br />
           this.textFieldError(input);<br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       dropdownValidation: function(field, input) {<br />
         if (input.value) {<br />
           field.className = field.className.replace(/ invalid/g, '');<br />
         } else {<br />
           if (field.className.indexOf('invalid') === -1) field.className += ' invalid';<br />
           this.onSelectCallback(input);<br />
           this.isValid = false;<br />
         }<br />
       },<br />
    <br />
       htmlEmbedDropdownValidation: function(field) {<br />
         var dropdowns = field.querySelectorAll('.mimi_html_dropdown');<br />
         var _this = this;<br />
    <br />
         for (var i = 0; i &lt; dropdowns.length; ++i) {<br />
           var dropdown = dropdowns[i];<br />
    <br />
           if (dropdown.value) {<br />
             field.className = field.className.replace(/ invalid/g, '');<br />
           } else {<br />
             if (field.className.indexOf('invalid') === -1) field.className += ' invalid';<br />
             this.isValid = false;<br />
             dropdown.onchange = (function(){ _this.validate(); });<br />
           }<br />
         }<br />
       },<br />
    <br />
       textFieldError: function(input) {<br />
         input.className   = 'required invalid';<br />
         input.placeholder = input.getAttribute('data-required-field');<br />
       },<br />
    <br />
       removeTextFieldError: function(input) {<br />
         input.className   = 'required';<br />
         input.placeholder = '';<br />
       },<br />
    <br />
       onSelectCallback: function(input) {<br />
         if (typeof Widget === 'undefined' || !Widget.BasicDropdown) return;<br />
    <br />
         var dropdownEl = input.parentNode,<br />
             instances  = Widget.BasicDropdown.instances,<br />
             _this = this;<br />
    <br />
         for (var i = 0; i &lt; instances.length; ++i) {<br />
           var instance = instances[i];<br />
           if (instance.wrapperEl === dropdownEl) {<br />
             instance.onSelect = function(){ _this.validate() };<br />
           }<br />
         }<br />
       },<br />
    <br />
       updateFormAfterValidation: function() {<br />
         this.form.className   = this.setFormClassName();<br />
         this.submit.value     = this.submitButtonText();<br />
         this.submit.disabled  = !this.isValid;<br />
         this.submit.className = this.isValid ? 'submit' : 'disabled';<br />
       },<br />
    <br />
       setFormClassName: function() {<br />
         var name = this.form.className;<br />
    <br />
         if (this.isValid) {<br />
           return name.replace(/\s?mimi_invalid/, '');<br />
         } else {<br />
           if (name.indexOf('mimi_invalid') === -1) {<br />
             return name += ' mimi_invalid';<br />
           } else {<br />
             return name;<br />
           }<br />
         }<br />
       },<br />
    <br />
       submitButtonText: function() {<br />
         var invalidFields = document.querySelectorAll('.invalid'),<br />
             text;<br />
    <br />
         if (this.isValid || !invalidFields) {<br />
           text = this.submit.getAttribute('data-default-text');<br />
         } else {<br />
           if (invalidFields.length || invalidFields[0].className.indexOf('checkgroup') === -1) {<br />
             text = this.submit.getAttribute('data-invalid-text');<br />
           } else {<br />
             text = this.submit.getAttribute('data-choose-list');<br />
           }<br />
         }<br />
         return text;<br />
       },<br />
    <br />
       submitForm: function() {<br />
         this.formSubmitting();<br />
    <br />
         var _this = this;<br />
         window[this.callbackName] = function(response) {<br />
           delete window[this.callbackName];<br />
           document.body.removeChild(script);<br />
           _this.onSubmitCallback(response);<br />
         };<br />
    <br />
         var script = document.createElement('script');<br />
         script.src = this.formUrl('json');<br />
         document.body.appendChild(script);<br />
       },<br />
    <br />
       formUrl: function(format) {<br />
         var action  = this.form.action;<br />
         if (format === 'json') action += '.json';<br />
         return action + '?callback=' + this.callbackName + '&amp;' + serialize(this.form);<br />
       },<br />
    <br />
       formSubmitting: function() {<br />
         this.form.className  += ' mimi_submitting';<br />
         this.submit.value     = this.submit.getAttribute('data-submitting-text');<br />
         this.submit.disabled  = true;<br />
         this.submit.className = 'disabled';<br />
       },<br />
    <br />
       onSubmitCallback: function(response) {<br />
         if (response.success) {<br />
           this.onSubmitSuccess(response.result);<br />
         } else {<br />
           top.location.href = this.formUrl('html');<br />
         }<br />
       },<br />
    <br />
       onSubmitSuccess: function(result) {<br />
         if (result.has_redirect) {<br />
           top.location.href = result.redirect;<br />
         } else if(result.single_opt_in || !result.confirmation_html) {<br />
           this.disableForm();<br />
           this.updateSubmitButtonText(this.submit.getAttribute('data-thanks'));<br />
         } else {<br />
           this.showConfirmationText(result.confirmation_html);<br />
         }<br />
       },<br />
    <br />
       showConfirmationText: function(html) {<br />
         var fields = this.form.querySelectorAll('.mimi_field');<br />
    <br />
         for (var i = 0; i &lt; fields.length; ++i) {<br />
           fields[i].style['display'] = 'none';<br />
         }<br />
    <br />
         (this.form.querySelectorAll('fieldset')[0] || this.form).innerHTML = html;<br />
       },<br />
    <br />
       disableForm: function() {<br />
         var elements = this.form.elements;<br />
         for (var i = 0; i &lt; elements.length; ++i) {<br />
           elements[i].disabled = true;<br />
         }<br />
       },<br />
    <br />
       updateSubmitButtonText: function(text) {<br />
         this.submit.value = text;<br />
       },<br />
    <br />
       revalidateOnChange: function() {<br />
         var fields = this.form.querySelectorAll(&quot;.mimi_field.required&quot;),<br />
             _this = this;<br />
    <br />
         var onTextFieldChange = function() {<br />
           if (this.getAttribute('name') === 'signup[email]') {<br />
             if (_this.validEmail.test(this.value)) _this.validate();<br />
           } else {<br />
             if (this.value.length === 1) _this.validate();<br />
           }<br />
         }<br />
    <br />
         for (var i = 0; i &lt; fields.length; ++i) {<br />
           var inputs = fields[i].getElementsByTagName('input');<br />
           for (var j = 0; j &lt; inputs.length; ++j) {<br />
             if (this.fieldType(fields[i]) === 'text_field') {<br />
               inputs[j].onkeyup = onTextFieldChange;<br />
               inputs[j].onchange = onTextFieldChange; <br />
             } else {<br />
               inputs[j].onchange = function(){ _this.validate() };<br />
             }<br />
           }<br />
         }<br />
       }<br />
     });<br />
    <br />
     if (document.addEventListener) {<br />
       document.addEventListener(&quot;DOMContentLoaded&quot;, function() {<br />
         new Mimi.Signups.EmbedValidation();<br />
       });<br />
     }<br />
     else {<br />
       window.attachEvent('onload', function() {<br />
         new Mimi.Signups.EmbedValidation();<br />
       });<br />
     }<br />
    })(this);<br />
    &lt;/script&gt;