<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[7217] sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools: WordCamp: Move camptix-network-tools plugin to Meta repo</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/7217">7217</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/7217","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>coreymckrill</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-05-22 22:57:38 +0000 (Tue, 22 May 2018)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>WordCamp: Move camptix-network-tools plugin to Meta repo

This removes the external that links to plugins.svn.wordpress.org and 
adds the files from the plugin directly to meta.svn.wordpress.org.

CampTix Network Tools is no longer maintained for use outside of WordCamp.org,
so this makes the Meta repo the canonical open source of the plugin.</pre>

<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsTODOtxt">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/TODO.txt</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolscamptixnetworktoolsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/camptix-network-tools.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsincludesclasscamptixnetworkattendeeslisttablephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-attendees-list-table.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsincludesclasscamptixnetworkdashboardlisttablephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-dashboard-list-table.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsincludesclasscamptixnetworkloglisttablephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-log-list-table.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/languages/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolslanguagescamptixnetworktoolspot">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/languages/camptix-network-tools.pot</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsnetworkdashboardphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/network-dashboard.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsreadmetxt">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/readme.txt</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot1png">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-1.png</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot2png">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-2.png</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot3png">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-3.png</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot4png">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-4.png</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentplugins">sites/trunk/wordcamp.org/public_html/wp-content/plugins/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<span class="cx" style="display: block; padding: 0 10px">Index: sites/trunk/wordcamp.org/public_html/wp-content/plugins
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins      2018-05-22 05:58:57 UTC (rev 7216)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins       2018-05-22 22:57:38 UTC (rev 7217)
</ins><a id="sitestrunkwordcamporgpublic_htmlwpcontentplugins"></a>
<div class="propset"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Property changes: sites/trunk/wordcamp.org/public_html/wp-content/plugins</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnexternals"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: svn:externals</h4></div>
<span class="cx" style="display: block; padding: 0 10px"> camptix                         https://plugins.svn.wordpress.org/camptix/trunk/
</span><span class="cx" style="display: block; padding: 0 10px"> campt-indian-payment-gateway    https://plugins.svn.wordpress.org/campt-indian-payment-gateway/tags/1.6/
</span><span class="cx" style="display: block; padding: 0 10px"> camptix-kdcpay-gateway          https://plugins.svn.wordpress.org/camptix-kdcpay-gateway/tags/1.5.0/
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-camptix-network-tools           https://plugins.svn.wordpress.org/camptix-network-tools/trunk/
</del><span class="cx" style="display: block; padding: 0 10px"> camptix-mercadopago             https://plugins.svn.wordpress.org/camptix-mercadopago/tags/1.0.6/
</span><span class="cx" style="display: block; padding: 0 10px"> camptix-pagseguro               https://plugins.svn.wordpress.org/camptix-pagseguro/tags/1.5.5/
</span><span class="cx" style="display: block; padding: 0 10px"> camptix-payfast-gateway         https://plugins.svn.wordpress.org/camptix-payfast-gateway/tags/1.0.0/
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsTODOtxt"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/TODO.txt</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/TODO.txt                            (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/TODO.txt      2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,33 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+High priority
+       internationalize strings
+       refactor paypal stuff out of transacount id look so that it can be modular like camptix itself
+       currency too
+       assign all classes to global variables so they can be accessed by other plugins
+       automatically grab credentials from individual site options, instead of having to assign via filter callback?
+
+Medium priority
+       add ability to send any 'critical' errors to array of admins
+       add error notice if camptix isn't activated?
+       log list table::prepare_items()
+               probably need to refactor how $advanced_query_value[x] works if add other cases with different number of substitutions.
+               since prepare() can take an array of args, maybe do an array_merge() of common arguments and an variable-length array of advanced query arguments
+               or just prepare() it here, and cat it with $where below after $where runs through prepare()
+               also, maybe avoid doing the regular query if doing an advanced query
+       add note to network log search results to scroll down to see the highlighted entry. otherwise they might not realize it's there b/c it's below the fold.
+       setup cron jobs to schedule upon plugin activation and unschedule upon deactivation, rather than always firing in the constructor?
+       revisit limit of 1000 in sql queries for blog_ids. large networks like wordcamp.org could start running up against that. at least put a filter around the value
+
+Low priority
+       fire gather_events_data job whenever new site is added to network, or when camptix activated?
+       add link to dashboard from camptix proper to help let people know about it?
+       camptix_log_email_notifications()
+               Maybe send batch of notifications via a cron job, instead of immediatetly sending an e-mail for each match
+               If current approach proves to be too expensive during traffic spikes (like when ticket sales open for a big event), we could setup a cron job to run every 10 minutes and scan new entries since it last ran.
+       setup $table_name as class var instead of multiple times in diff functions so the logic is DRY
+               set as public var so other classes can use it
+       gather_events_data()
+               store time in utc, but display in current site's timezone
+               put filter around 1000 limit. there are a few others places that need this too
+               being stored in redundantly individual tables rather than in global table
+       network log search for something like "id:refund" should probably return 0 results
+       remove reference to active_plugins filter since it's deprecated
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolscamptixnetworktoolsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/camptix-network-tools.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/camptix-network-tools.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/camptix-network-tools.php     2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,268 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/*
+* Plugin Name: CampTix Network Tools
+* Plugin URI: http://wordcamp.org
+* Description: Tools for managing CampTix installations across a WordPress Multisite network.
+* Version: 0.2
+* Author: Automattic
+* Author URI: http://wordcamp.org
+* License: GPLv2 or later
+* Network: true
+*/
+
+class CampTix_Network_Tools {
+       private $options;
+       private $db_version = 20131202;
+       const PLUGIN_URL = "http://wordpress.org/plugins/camptix-network-tools";
+
+       function __construct() {
+               add_action( 'init',             array( $this, 'init' ) );
+               add_action( 'camptix_pre_init', array( $this, 'camptix_pre_init' ) );
+               add_action( 'camptix_init',     array( $this, 'camptix_init' ) );
+       }
+
+       function init() {
+               $this->options = array_merge( array(
+                       'db_version' => 0,
+               ), get_site_option( 'camptix_nt_options', array() ) );
+               $this->options = $this->validate_options( $this->options );
+
+               if ( $this->options['db_version'] != $this->db_version ) {
+                       $this->upgrade();
+                       update_site_option( 'camptix_nt_options', $this->options );
+               }
+       }
+
+       function validate_options( $options ) {
+               $options['db_version'] = absint( $options['db_version'] );
+
+               return $options;
+       }
+
+       function upgrade() {
+               global $wpdb;
+
+               $charset_collate = '';
+               if ( ! empty( $wpdb->charset ) )
+                       $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
+               if ( ! empty( $wpdb->collate ) )
+                       $charset_collate .= " COLLATE $wpdb->collate";
+
+               $table_name = $wpdb->base_prefix . "camptix_log";
+               $sql = "CREATE TABLE $table_name (
+                       id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+                       timestamp timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
+                       blog_id bigint(20) NOT NULL,
+                       object_id bigint(20) NOT NULL,
+                       message text NOT NULL,
+                       section varchar(32) DEFAULT 'general',
+                       data mediumtext NOT NULL,
+                       UNIQUE KEY id (id)
+               ) $charset_collate;";
+
+               require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
+               dbDelta( $sql );
+               $this->options['db_version'] = $this->db_version;
+       }
+
+       function camptix_pre_init() {
+               add_action( 'camptix_log_raw',        array( $this, 'camptix_log_raw' ), 10, 4 );
+               add_action( 'camptix_log_raw',        array( $this, 'camptix_log_email_notifications' ), 10, 4 );
+               add_filter( 'camptix_default_addons', array( $this, 'camptix_default_addons' ) );
+       }
+
+       function camptix_init() {
+               add_action( 'camptix_add_meta_boxes', array( $this, 'camptix_add_meta_boxes' ), 11 );
+       }
+
+       // Disable logging to postmeta tables since we'll be logging to a dedicated, global table instead
+       function camptix_default_addons( $addons ) {
+               unset( $addons['logging-meta'] );
+               return $addons;
+       }
+
+       function camptix_add_meta_boxes() {
+               $post_types = array(
+                       'tix_attendee',
+                       'tix_ticket',
+                       'tix_email',
+                       'tix_coupon',
+               );
+               foreach ( $post_types as $post_type )
+                       add_meta_box( 'tix_db_log', 'CampTix DB Log', array( $this, 'metabox_log' ), $post_type, 'normal' );
+       }
+
+       /**
+        * CampTix Log metabox for various post types.
+        */
+       function metabox_log() {
+               global $post, $camptix, $wpdb;
+
+               if ( ! get_current_blog_id() || ! $post->ID )
+                       return;
+
+               $rows = array();
+               $table_name = $wpdb->base_prefix . "camptix_log";
+               $entries = (array) $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name WHERE blog_id = %d AND object_id = %d ORDER BY id ASC;", get_current_blog_id(), $post->ID ) );
+
+               // Add entries as rows.
+               foreach ( $entries as $entry ) {
+                       $message = esc_html( $entry->message );
+                       $data = json_decode( $entry->data );
+                       if ( $data ) {
+                               $message .= ' <a href="#" class="tix-more-bytes">data</a>';
+                               $message .= '<pre class="tix-bytes" style="display: none;">' . esc_html( print_r( $data, true ) ) . '</pre>';
+                       }
+                       $rows[] = array( date( 'Y-m-d H:i:s', strtotime( $entry->timestamp ) ), $message );
+               }
+
+               if ( count( $rows ) < 1 )
+                       $rows[] = array( 'No log entries yet.', '' );
+
+               $camptix->table( $rows, 'tix-log-table' );
+               ?>
+
+               <p class="description">Note: Some relevant log entries may not be displayed here. Check the global error log to get a complete picture of activity.</p>
+
+               <script>
+                       jQuery('.tix-more-bytes').click(function() {
+                               jQuery(this).parent().find('.tix-bytes').toggle();
+                               return false;
+                       });
+               </script>
+
+       <?php
+       }
+
+       /**
+        * Logs to a db
+        */
+       function camptix_log_raw( $message, $post_id, $data_raw, $section = 'general' ) {
+               global $wpdb, $blog_id, $camptix;
+
+               if ( is_null( $post_id ) ) {
+                       $post_id = 0;
+               }
+
+               $data = json_encode( stripslashes_deep( $data_raw ) );
+               $json_last_error = json_last_error();
+               if ( JSON_ERROR_NONE != $json_last_error ) {
+                       $data = sprintf( 'json_encode() error: code #%d. Raw data was: %s', $json_last_error, print_r( $data_raw, true ) );
+               }
+
+               $table_name = $wpdb->base_prefix . "camptix_log";
+               $wpdb->insert( $table_name, array(
+                       'blog_id' => $blog_id,
+                       'object_id' => $post_id,
+                       'message' => $message,
+                       'data' => $data,
+                       'section' => $section,
+               ) );
+               $camptix->tmp( 'last_log_id', $wpdb->insert_id );
+
+               $entry = array(
+                       'url' => home_url(),
+                       'timestamp' => time(),
+                       'message' => $message,
+                       'data' => $data,
+                       'module' => $section,
+               );
+
+               if ( $post_id ) {
+                       $entry['post_id'] = absint( $post_id );
+                       $entry['edit_post_link'] = esc_url_raw( add_query_arg( array( 'post' => absint( $post_id ), 'action' => 'edit' ), admin_url( 'post.php' ) ) );
+               }
+
+               // (optional) Also write the message to the standard log file
+               if ( isset( $entry['message'] ) && apply_filters( 'camptix_nt_file_log', true ) ) {
+                       $url = parse_url( home_url() );
+                       $prefix = sprintf( 'CampTix (%s): ', $url['host'] );
+                       error_log( $prefix . $entry['message'] );
+               }
+       }
+
+       /*
+        * Sends e-mail notifications on log events that match pre-defined regular expressions
+        */
+       function camptix_log_email_notifications( $message, $post_id, $data, $section ) {
+               global $camptix;
+
+               $expressions = apply_filters( 'camptix_nt_notification_expressions', array() );
+               $expressions = $this->update_notification_expressions_format( $expressions );
+
+               if ( $expressions ) {
+                       foreach ( $expressions as $expression ) {
+                               if ( preg_match( $expression['pattern'], $message .' '. print_r( $data, true ) ) ) {
+                                       if ( is_int( $post_id ) && $post_id > 0 ) {
+                                               $user = "\nUser: " . esc_html( get_the_title( $post_id ) ) . ' (<'. esc_url_raw( get_admin_url( null, '/post.php?post='. $post_id .'&action=edit' ) ) .'>)';
+                                       } else {
+                                               $user = '';
+                                       }
+
+                                       $subject = 'CampTix Log Notification';
+                                       if ( ! empty( $expression['subject'] ) ) {
+                                               $subject .= ': '. sanitize_text_field( $expression['subject'] );
+                                       }
+
+                                       $email_body = sprintf(
+                                               "%s\n\nSite: %s%s\nMessage: %s\nRegular Expression: %s\nTimestamp: %s\n\nMore information is available in the Network Log: <%s>",
+                                               ! empty( $expression['message'] ) ? esc_html( $expression['message'] ) : "The following CampTix log entry matches an expression you've subscribed to.",
+                                               get_bloginfo( 'name' ),
+                                               $user,
+                                               esc_html( $message ),
+                                               esc_html( $expression['pattern'] ),
+                                               date( 'Y-m-d H:i:s' ),  // assumes MySQL timezone matches PHP timezone, and that next clock tick hasn't occurred after record insertion
+                                               add_query_arg(
+                                                       array(
+                                                               'tix_section' => 'log',
+                                                               'page' => 'camptix-dashboard',
+                                                               's' => 'id:' . absint( $camptix->tmp( 'last_log_id' ) ),
+                                                       ),
+                                                       network_admin_url()
+                                               )       // assumes recipient has access to Network Log
+                                       );
+
+                                       wp_mail( $expression['addresses'], $subject, $email_body );
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Update notifications to the current format.
+        *
+        * The previous formation was:
+        *
+        * array(
+        *     'pattern1' => array( 'address1', 'address2, 'etc' ),
+        *     'pattern2  => array( 'address1', 'address2, 'etc' ),
+        * )
+        *
+        * This allows us to update to a more verbose and flexible format without breaking backwards compatibility.
+        *
+        * @param array $expressions
+        * @return array
+        */
+       protected function update_notification_expressions_format( $expressions ) {
+               foreach ( $expressions as $key => $value ) {
+                       if ( ! isset( $value['subject'] ) ) {
+                               $expressions[ $key ] = array(
+                                       'subject'   => '',
+                                       'message'   => '',
+                                       'pattern'   => $key,
+                                       'addresses' => $value,
+                               );
+                       }
+               }
+
+               return $expressions;
+       }
+
+       function __destruct() {
+               if ( isset( $this->log_file ) && $this->log_file )
+                       fclose( $this->log_file );
+       }
+}
+
+$GLOBALS['camptix_network_tools'] = new CampTix_Network_Tools();
+require_once( plugin_dir_path( __FILE__ ) . 'network-dashboard.php' );
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsincludesclasscamptixnetworkattendeeslisttablephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-attendees-list-table.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-attendees-list-table.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-attendees-list-table.php       2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,112 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+class CampTix_Network_Attendees_List_Table extends WP_List_Table {
+       
+       public $max_results = 100;
+       
+       function get_columns() {
+               return array(
+                       'tix_date' => 'Date',
+                       'tix_name' => 'Name',
+                       'tix_email' => 'E-mail',
+                       'tix_event' => 'Event',
+               );
+       }
+               
+       function prepare_items() {
+               global $wpdb;
+
+               if ( ! isset( $_POST['s'] ) || empty( $_POST['s'] ) )
+                       return;
+               check_admin_referer( 'dashboard_attendees_search_query', 'dashboard_attendees_search_query_nonce' );
+
+               $search_query = trim( $_POST['s'] );
+               $results = array();
+
+               $blogs = $wpdb->get_col( $wpdb->prepare(
+                       "SELECT blog_id FROM `{$wpdb->blogs}` WHERE site_id = %d ORDER BY last_updated DESC LIMIT %d;",
+                       $wpdb->siteid,
+                       apply_filters( 'camptix_nt_attendee_list_blog_limit', 1500 )
+               ) );
+               foreach ( $blogs as $bid ) {
+                       
+                       if ( count( $results ) >= $this->max_results )
+                               break;
+                       
+                       switch_to_blog( $bid );
+
+                       if ( is_plugin_active( 'camptix/camptix.php') ) {
+                                       $paged = 1;
+                                       while ( $attendees = get_posts( array(
+                                               'paged' => $paged++,
+                                               'post_status' => array( 'publish', 'pending' ),
+                                               'post_type' => 'tix_attendee',
+                                               'posts_per_page' => 20,
+                                               's' => $search_query,
+                                       ) ) ) {
+                                               foreach ( $attendees as $attendee ) {
+                                                       
+                                                       // Out of the foreach $attendees and while loop, but not the $blogs foreach loop.
+                                                       if ( count( $results ) >= $this->max_results )
+                                                               break 2;
+                                                       
+                                                       $results[] = array(
+                                                               'attendee' => $attendee,
+                                                               'meta' => get_post_custom( $attendee->ID ),
+                                                               'event' => array(
+                                                                       'name' => get_bloginfo( 'name' ),
+                                                                       'url' => home_url( '/' ),
+                                                                       'edit_post_link' => add_query_arg( array(
+                                                                               'post' => $attendee->ID,
+                                                                               'action' => 'edit',
+                                                                       ), admin_url( 'post.php' ) ),
+                                                               ),
+                                                       );
+                                                       clean_post_cache( $attendee->ID );
+                                               }
+                                       }
+                               }
+                       
+                       restore_current_blog();
+               }
+
+               $this->items = $results;
+
+               /*$total_items = count( $output );
+               $total_pages = 5;
+               
+               $this->set_pagination_args( array(
+                       'total_items' => 0,
+                       'total_pages' => 99,
+                       'per_page' => $per_page,
+               ) );*/
+       }
+       
+       function column_tix_date( $item ) {
+               extract( $item ); // $attendee, $meta, $event
+               return date( 'Y-m-d', strtotime( $attendee->post_date ) );
+       }
+       
+       function column_tix_name( $item ) {
+               extract( $item ); // $attendee, $meta, $event
+               return sprintf( '<a href="%s">%s</a>', esc_url( $event['edit_post_link'] ), esc_html( $attendee->post_title ) );
+       }
+       
+       function column_tix_email( $item ) {
+               extract( $item ); // $attendee, $meta, $event
+               $email = '';
+
+               if ( isset( $meta['tix_email'], $meta['tix_email'][0] ) && is_email( $meta['tix_email'][0] ) )
+                       $email = $meta['tix_email'][0];
+
+               return sprintf( '<a href="mailto:%s">%s</a>', esc_attr( $email ), esc_html( $email ) );
+       }
+       
+       function column_tix_event( $item ) {
+               extract( $item ); // $attendee, $meta, $event
+               return sprintf( '<a href="%s">%s</a>', esc_url( $event['url'] ), esc_html( $event['name'] ) );
+       }
+       
+       function column_default( $item, $column_name ) {
+               return 'default';
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsincludesclasscamptixnetworkdashboardlisttablephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-dashboard-list-table.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-dashboard-list-table.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-dashboard-list-table.php       2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,218 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+class CampTix_Network_Dashboard_List_Table extends WP_List_Table {
+       
+       function get_columns() {
+               return array(
+                       'tix_event' => 'Event',
+                       'tix_start' => 'Sales Open',
+                       'tix_end' => 'Sales Close',
+                       'tix_sold' => 'Sold',
+                       'tix_remaining' => 'Remaining',
+                       'tix_subtotal' => 'Sub-Total',
+                       'tix_discounted' => 'Discounted',
+                       'tix_revenue' => 'Revenue',
+                       'tix_version' => 'Version',
+               );
+       }
+       
+       function get_sortable_columns() {
+               return array(
+                       'tix_event' => 'tix_event',
+                       'tix_start' => 'tix_start',
+                       'tix_end' => 'tix_end',
+                       'tix_sold' => 'tix_sold',
+                       'tix_remaining' => 'tix_remaining',
+                       'tix_subtotal' => 'tix_subtotal',
+                       'tix_discounted' => 'tix_discounted',
+                       'tix_revenue' => 'tix_revenue',
+               );
+       }
+
+       function get_views() {
+               return array(
+                       'all' => '<a href="' . esc_url( remove_query_arg( 'tix_view' ) ) . '">All</a>',
+                       'active' => '<a href="' . esc_url( remove_query_arg( 'tix_view' ) ) . '">Active</a>',
+                       'sandbox' => '<a href="' . esc_url( remove_query_arg( 'tix_view' ) ) . '">Sandboxed</a>',
+                       'archived' => '<a href="' . esc_url( remove_query_arg( 'tix_view' ) ) . '">Archived</a>',
+               );
+       }
+       
+       function prepare_items() {
+               
+               $this->currency = 'USD';
+               $paged = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 1;
+               $per_page = apply_filters( 'camptix_nt_overview_per_page', 10 );
+               
+               $args = array(
+                       'post_type' => 'tix_event',
+                       'post_status' => 'any',
+                       'posts_per_page' => $per_page,
+                       'paged' => $paged,
+                       'meta_query' => array(),
+               );
+
+               // Exclude archived sites
+               $args['meta_query'][] = array(
+                       'key' => 'tix_archived',
+                       'compare' => '=',
+                       'value' => '0',
+               );
+
+               if ( isset( $_REQUEST['orderby'] ) ) {
+                       $orderby = strtolower( $_REQUEST['orderby'] );
+                       
+                       switch ( $orderby ) {
+                               case 'tix_event':
+                                       $args['orderby'] = 'title';
+                                       break;
+                               case 'tix_sold':
+                                       $args['orderby'] = 'meta_value_num';
+                                       $args['meta_key'] = 'tix_stats_sold';
+                                       break;
+                               case 'tix_remaining':
+                                       $args['orderby'] = 'meta_value_num';
+                                       $args['meta_key'] = 'tix_stats_remaining';
+                                       break;
+                               case 'tix_subtotal':
+                                       $args['orderby'] = 'meta_value_num';
+                                       $args['meta_key'] = 'tix_stats_subtotal';
+                                       break;
+                               case 'tix_discounted':
+                                       $args['orderby'] = 'meta_value_num';
+                                       $args['meta_key'] = 'tix_stats_discounted';
+                                       break;
+                               case 'tix_revenue':
+                                       $args['orderby'] = 'meta_value_num';
+                                       $args['meta_key'] = 'tix_stats_revenue';
+                                       break;
+                               case 'tix_start':
+                                       $args['orderby'] = 'meta_value_num';
+                                       $args['meta_key'] = 'tix_earliest_start';
+                                       break;
+                               case 'tix_end':
+                                       $args['orderby'] = 'meta_value_num';
+                                       $args['meta_key'] = 'tix_latest_end';
+                                       break;
+                               default:
+                       }
+               }
+               
+               $args['order'] = ( isset( $_REQUEST['order'] ) && in_array( $_REQUEST['order'], array( 'asc', 'desc' ) ) ) ? $_REQUEST['order'] : 'asc';
+               
+               if ( isset( $_REQUEST['s'] ) )
+               {
+                       check_admin_referer( 'dashboard_overview_search_events', 'dashboard_overview_search_events_nonce' );
+                       $args['s'] = $_REQUEST['s'];
+               }
+               
+               $query = new WP_Query( $args );
+               $this->items = $query->posts;
+
+               $total_items = $query->found_posts;
+               $total_pages = $query->max_num_pages;
+               
+               $this->set_pagination_args( array(
+                       'total_items' => $total_items,
+                       'total_pages' => $total_pages,
+                       'per_page' => $per_page
+               ) );
+       }
+
+       function column_tix_event( $item ) {
+               $home_url = esc_url( get_post_meta( $item->ID, 'tix_home_url', true ) );
+               $admin_url = esc_url( get_post_meta( $item->ID, 'tix_admin_url', true ) );
+               $options = get_post_meta( $item->ID, 'tix_options', true );
+               
+               $revenue_url = add_query_arg( array( 'post_type' => 'tix_ticket', 'page' => 'camptix_tools', 'tix_section' => 'revenue' ), $admin_url . 'edit.php' );
+               $return = '<strong><a href="' . $home_url . '" class="row-title">' . $item->post_title . '</a></strong>';
+               
+               $extra = array();
+               if ( isset( $options['paypal_sandbox'] ) && $options['paypal_sandbox'] )
+                       $extra[] = 'Sandbox';
+               
+               if ( isset( $options['archived'] ) && $options['archived'] )
+                       $extra[] = 'Archived';
+                       
+               if ( $extra )
+                       $return .= ' - ' . implode( ', ', $extra );
+               
+               $actions = array(
+                       'dashboard' => '<span><a href="' . $admin_url . '">Dashboard</a></span>',
+                       'revenue' => '<span><a href="' . $revenue_url . '">Revenue Report</a></span>',
+                       'visit' => '<span><a href="' . $home_url . '">Visit</a></span>',
+               );
+               $return .= $this->row_actions( $actions );
+               return $return;
+       }
+       
+       function column_tix_sold( $item ) {
+               return intval( get_post_meta( $item->ID, 'tix_stats_sold', true ) );
+       }
+       
+       function column_tix_remaining( $item ) {
+               return intval( get_post_meta( $item->ID, 'tix_stats_remaining', true ) );
+       }
+       
+       function column_tix_subtotal( $item ) {
+               return $this->append_currency( (float) get_post_meta( $item->ID, 'tix_stats_subtotal', true ) );
+       }
+       
+       function column_tix_discounted( $item ) {
+               return $this->append_currency( (float) get_post_meta( $item->ID, 'tix_stats_discounted', true ) );
+       }
+       
+       function column_tix_revenue( $item ) {
+               return $this->append_currency( (float) get_post_meta( $item->ID, 'tix_stats_revenue', true ) );
+       }
+       
+       function column_tix_start( $item ) {
+               $start = intval( get_post_meta( $item->ID, 'tix_earliest_start', true ) );
+               $undefined_start = (bool) get_post_meta( $item->ID, 'tix_undefined_start', true );
+               
+               if ( $undefined_start )
+                       return 'Undefined';
+               
+               if ( $start ) {
+                       $ago = human_time_diff( $start, time() );
+                       $ago .= $start < time() ? ' ago' : ' from now';
+                       return '<acronym class="tix-tooltip" title="' . esc_attr( $ago ) . '">' . esc_html( date( 'Y-m-d', $start ) ) . '</acronym>';
+               }
+       }
+       
+       function column_tix_end( $item ) {
+               $end = intval( get_post_meta( $item->ID, 'tix_latest_end', true ) );
+               $undefined_end = (bool) get_post_meta( $item->ID, 'tix_undefined_end', true );
+               
+               if ( $undefined_end )
+                       return 'Undefined';
+               
+               if ( $end ) {
+                       $ago = human_time_diff( $end, time() );
+                       $ago .= $end < time() ? ' ago' : ' from now';
+                       return '<acronym class="tix-tooltip" title="' . esc_attr( $ago ) . '">' . esc_html( date( 'Y-m-d', $end ) ) . '</acronym>';
+               }
+       }
+       
+       function column_tix_version( $item ) {
+               $options = get_post_meta( $item->ID, 'tix_options', true );
+               if ( isset( $options['version'] ) )
+                       return $options['version'];
+       }
+       
+       function column_default( $item, $column_name ) {
+               return 'default';
+       }
+       
+       function single_row( $item ) {
+               
+               $options = get_post_meta( $item->ID, 'tix_options', true );
+               if ( isset( $options['paypal_currency'] ) )
+                       $this->currency = $options['paypal_currency'];
+               
+               parent::single_row( $item );
+       }
+       
+       function append_currency( $price ) {
+               return sprintf( "%s %s", number_format( (float) $price, 2 ), $this->currency );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsincludesclasscamptixnetworkloglisttablephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-log-list-table.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-log-list-table.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/includes/class-camptix-network-log-list-table.php     2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,133 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+class CampTix_Network_Log_List_Table extends WP_List_Table {
+       var $log_highlight_id = false;
+
+       function get_columns() {
+               return array(
+                       'tix_timestamp' => 'Timestamp',
+                       'tix_message' => 'Message',
+                       'tix_domain' => 'Domain',
+               );
+       }
+
+       function prepare_items() {
+               global $wpdb;
+
+               $per_page = (int) apply_filters( 'camptix_nt_log_entries_per_page', 50 );
+               $paged = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 1;
+               $offset = $per_page * ( $paged - 1 );
+               $where = ' WHERE 1=1 ';
+
+               if ( isset( $_REQUEST['s'] ) ) {
+                       $s = $_REQUEST['s'];
+                       $advanced_query = explode( ':', $s );
+
+                       switch ( $advanced_query[0] ) {
+                               case 'id':
+                                       $this->log_highlight_id = absint( $advanced_query[1] );
+                                       $range = floor( $per_page / 2 );
+
+                                       $advanced_query_value1 = $this->log_highlight_id - $range;
+                                       $advanced_query_value2 = $this->log_highlight_id + $range - 1;       // - 1 to avoid pagination
+                                       $advanced_query = "OR id BETWEEN %d AND %d";
+                               break;
+
+                               default:
+                                       $advanced_query = $advanced_query_value1 = $advanced_query_value2 = '';
+                               break;
+                       }
+
+                       $where .= $wpdb->prepare( " AND ( message LIKE '%%%s%%' OR data LIKE '%%%s%%' OR ( object_id = '%s' AND object_id > 0 ) ". $advanced_query ." )",
+                               like_escape( $s ), like_escape( $s ), $s, $advanced_query_value1, $advanced_query_value2
+                       );
+               }
+
+               if ( isset( $_REQUEST['tix_log_section'] ) ) {
+                       $section = $_REQUEST['tix_log_section'];
+                       $where .= $wpdb->prepare ( " AND section = '%s' ", $section );
+               }
+
+               if ( isset( $_REQUEST['tix_log_blog_id'] ) ) {
+                       $bid = absint( $_REQUEST['tix_log_blog_id'] );
+                       $where .= $wpdb->prepare( " AND blog_id = %d ", $bid );
+               }
+
+               $orderby = " ORDER BY id DESC";
+               $limit = $wpdb->prepare( " LIMIT %d OFFSET %d ", $per_page, $offset );
+
+               $table_name = $wpdb->base_prefix . "camptix_log";
+               $this->items = $wpdb->get_results( "SELECT SQL_CALC_FOUND_ROWS * FROM $table_name $where $orderby $limit;" );
+               $found_rows = $wpdb->get_var( "SELECT FOUND_ROWS();" );
+
+               $this->set_pagination_args( array(
+                       'total_items' => $found_rows,
+                       'total_pages' => ceil( $found_rows / $per_page ),
+                       'per_page' => $per_page,
+               ) );
+       }
+
+       function column_tix_timestamp( $item ) {
+               $timestamp = $item->timestamp;
+               $ago = human_time_diff( strtotime( $timestamp ), time() ) . ' ago';
+               return '<acronym class="tix-tooltip tix-tooltip-' . absint( $item->id ) . '" title="' . esc_attr( $ago ) . '">' . esc_html( $timestamp ) . '</acronym>';
+       }
+
+       function column_tix_message( $item ) {
+               $message = esc_html( $item->message );
+               $actions = array();
+               $data = '';
+
+               if ( isset( $item->data ) && $item->data ) {
+                       $actions[] = '<a href="#" class="tix-more-bytes">data</a>';
+                       $data .= '<pre class="tix-bytes" style="display: none;">' . esc_html( print_r( $item->data, true ) ) . '</pre>';
+               }
+
+               if ( isset( $item->object_id ) && $item->object_id > 0 ) {
+                       $edit_url = get_admin_url( $item->blog_id, 'post.php' );
+                       $edit_url = add_query_arg( array(
+                               'post' => rawurlencode( $item->object_id ),
+                               'action' => 'edit',
+                       ), $edit_url );
+                       $actions[] = sprintf( '<a href="%s">%d</a>', esc_url( $edit_url ), $item->object_id );
+               }
+
+               $section = isset( $item->section ) ? esc_html( $item->section ) : 'general';
+               $url = add_query_arg( 'tix_log_section', $section, network_admin_url( 'index.php?tix_section=log&page=camptix-dashboard' ) );
+               $actions[] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), $section );
+
+               if ( $actions )
+                       $actions = ' <span class="tix-network-log-actions">' . implode( ', ', $actions ) . '</span>';
+               else
+                       $actions = '';
+
+               return $message . $actions . $data;
+       }
+
+       function column_tix_domain( $item ) {
+               $url = str_replace( array( 'http://', 'https://' ), '', esc_url( get_home_url( $item->blog_id ) ) );
+               $link = add_query_arg( 'tix_log_blog_id', rawurlencode( $item->blog_id ), network_admin_url( 'index.php?tix_section=log&page=camptix-dashboard' ) );
+
+               return sprintf( '<a href="%s">%s</a>', esc_url( $link ), $url );
+       }
+
+       function column_default( $item, $column_name ) {
+               return 'default';
+       }
+
+       function single_row( $item ) {
+               static $row_class = array( 'alternate' => false, 'highlight' => false );
+
+               $row_class['alternate'] = ! $row_class['alternate'];
+               $row_class['highlight'] = $item->id == $this->log_highlight_id ? true : false;
+
+               $data_json = json_decode( $item->data );
+               if ( JSON_ERROR_NONE == json_last_error() ) {
+                       $item->data = $data_json;
+               }
+
+               echo '<tr class="' . ( $row_class['alternate'] ? 'alternate' : '' ) . ( $row_class['highlight'] ? ' highlight' : '' ) . '">';
+               echo $this->single_row_columns( $item );
+               echo '</tr>';
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolslanguagescamptixnetworktoolspot"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/languages/camptix-network-tools.pot</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/languages/camptix-network-tools.pot                         (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/languages/camptix-network-tools.pot   2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,35 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+# Copyright (C) 2014 CampTix Network Tools
+# This file is distributed under the same license as the CampTix Network Tools package.
+msgid ""
+msgstr ""
+"Project-Id-Version: CampTix Network Tools 0.1\n"
+"Report-Msgid-Bugs-To: http://wordpress.org/support/plugin/camptix-network-"
+"tools\n"
+"POT-Creation-Date: 2014-07-11 13:10:01+00:00\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2014-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+
+#. Plugin Name of the plugin/theme
+msgid "CampTix Network Tools"
+msgstr ""
+
+#. #-#-#-#-#  camptix-network-tools.pot (CampTix Network Tools 0.1)  #-#-#-#-#
+#. Plugin URI of the plugin/theme
+#. #-#-#-#-#  camptix-network-tools.pot (CampTix Network Tools 0.1)  #-#-#-#-#
+#. Author URI of the plugin/theme
+msgid "http://wordcamp.org"
+msgstr ""
+
+#. Description of the plugin/theme
+msgid ""
+"Tools for managing CampTix installations across a WordPress Multisite "
+"network."
+msgstr ""
+
+#. Author of the plugin/theme
+msgid "Automattic"
+msgstr ""
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsnetworkdashboardphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/network-dashboard.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/network-dashboard.php                               (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/network-dashboard.php 2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,475 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+class CampTix_Network_Dashboard {
+
+       protected $debug = false;
+
+       function __construct() {
+               add_action( 'network_admin_menu', array( $this, 'admin_menu' ) );
+               add_action( 'init', array( $this, 'init' ) );
+
+               $this->schedule_events();
+       }
+
+       function schedule_events() {
+               add_action( 'tix_dashboard_scheduled_hourly', array( $this, 'update_revenue_reports_data' ) );
+               add_action( 'tix_dashboard_scheduled_hourly', array( $this, 'gather_events_data' ), 11 );       // priority 11 so it runs after revenue data refreshed
+
+               // wp_clear_scheduled_hook( 'tix_scheduled_hourly' );
+               if ( ! wp_next_scheduled( 'tix_dashboard_scheduled_hourly' ) )
+                       wp_schedule_event( time(), 'hourly', 'tix_dashboard_scheduled_hourly' );
+       }
+
+       /*
+        * Update the CampTix revenue report data
+        *
+        * CampTix only updates the data when the user visits the Tools > Revenue tab, because that's the only time it needs to be updated in a single instance.
+        * In a Multisite network, though, CampTix Network Tools displays the data in the Overview tab, which can result in outdated and/or inaccurate data being
+        * displayed unless an admin of each individual site visits their revenue report frequently. This function ensures that the data is current by
+        * automatically updating it as part of a cron job
+        */
+       function update_revenue_reports_data() {
+               global $wpdb, $camptix;
+
+               if ( get_site_option( 'camptix_nt_revenue_report_last_run', 0 ) > ( time() - HOUR_IN_SECONDS ) )
+                       return;         // prevent the job from firing more often than intended because it'll be triggered by multiple sites in the network
+
+               // The cron job may be fired from a site that doesn't have CampTix loaded, so only run if it is loaded
+               if ( method_exists( $camptix, 'generate_revenue_report_data' ) ) {
+                       $remaining_blogs = get_site_option( 'camptix_nt_revenue_report_blog_ids', array() );
+                       if ( empty( $remaining_blogs ) ) {
+                               $remaining_blogs = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM `{$wpdb->blogs}` WHERE site_id = %d LIMIT 1000;", $wpdb->siteid ) );
+                       }
+
+                       $current_batch = array_splice( $remaining_blogs, 0, apply_filters( 'camptix_nt_revenue_report_batch_size', 30 ) );
+                       $camptix->log( 'Updating next batch of revenue reports.' );
+
+                       foreach ( $current_batch as $blog_id ) {
+                               switch_to_blog( $blog_id );
+
+                               if ( in_array( 'camptix/camptix.php', get_option( 'active_plugins', array() ) ) ) {
+                                       $camptix_options = get_option( 'camptix_options' );
+
+                                       if ( ! $camptix_options['archived'] ) {
+                                               $camptix->generate_revenue_report_data();
+                                       }
+                               }
+
+                               restore_current_blog();
+                       }
+
+                       update_site_option( 'camptix_nt_revenue_report_last_run', time() );
+                       if ( empty( $remaining_blogs ) ) {
+                               delete_site_option( 'camptix_nt_revenue_report_blog_ids' );
+                       } else {
+                               update_site_option( 'camptix_nt_revenue_report_blog_ids', $remaining_blogs );
+                       }
+               }
+       }
+
+       /**
+        * Gather data on all CampTix events for the Overview tab on the CampTix NT Dashboard
+        */
+       function gather_events_data() {
+               global $wpdb;
+
+               /*
+                * We only want this function to run on the main site.
+                *
+                * In most cases, that's ID 1, but not always. If the admin sets a different BLOG_ID_CURRENT_SITE in wp-config,
+                * then we still want to insert the data into site ID 1, because that's where the network dashboard is displayed,
+                * and consequentially, where the data is pulled from. If it were only inserted into the main site, then the
+                * Overview tab would be empty.
+                *
+                * Ideally we should display the dashboard on the main site instead of ID 1, but that would involve a lot of refactoring,
+                * and maybe even a core patch, in order to do it properly. So, this is an ugly hack to make it work.
+                */
+               if ( ! is_main_site() && 1 != get_current_blog_id() ) {
+                       return;
+               }
+
+               // Update timestamp.
+               update_option( 'camptix_dashboard_timestamp', time() );
+
+               // Remove old events.
+               $events = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'tix_event';" );
+               if ( is_array( $events ) && count( $events ) > 0 ) {
+                       $events_ids = implode( ',', $events );
+                       $wpdb->query( "DELETE FROM `{$wpdb->postmeta}` WHERE post_id IN ( $events_ids );" );
+                       $wpdb->query( "DELETE FROM `{$wpdb->posts}` WHERE ID IN ( $events_ids );" );
+               }
+
+               $blogs = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM `{$wpdb->blogs}` WHERE site_id = %d LIMIT 1000;", $wpdb->siteid ) );
+               foreach ( $blogs as $bid ) {
+                       switch_to_blog( $bid );
+
+                               $post = $meta = false;
+                               if ( in_array( 'camptix/camptix.php', (array) apply_filters( 'active_plugins', get_option( 'active_plugins', array() ) ) ) ) {
+
+                                       $options = get_option( 'camptix_options' );
+
+                                       $post = array(
+                                               'post_type' => 'tix_event',
+                                               'post_status' => 'publish',
+                                               'post_title' => get_bloginfo( 'name' ),
+                                       );
+
+                                       $meta = array(
+                                               'tix_options' => $options,
+                                               'tix_home_url' => home_url(),
+                                               'tix_admin_url' => admin_url(),
+                                       );
+
+                                       $stats = get_option( 'camptix_stats', array() );
+                                       foreach ( $stats as $key => $value )
+                                               $meta['tix_stats_' . $key] = $value;
+
+                                       $meta['tix_earliest_start'] = null;
+                                       $meta['tix_undefined_start'] = false;
+                                       $meta['tix_latest_end'] = null;
+                                       $meta['tix_undefined_end'] = false;
+
+                                       // Let's take a look at the tickets.
+                                       $paged = 1;
+                                       while ( $tickets = get_posts( array(
+                                               'post_type' => 'tix_ticket',
+                                               'post_status' => 'publish',
+                                               'paged' => $paged++,
+                                               'posts_per_page' => 10,
+                                       ) ) ) {
+
+                                               // Loop through tickets.
+                                               foreach ( $tickets as $ticket ) {
+
+                                                       $start = get_post_meta( $ticket->ID, 'tix_start', true );
+                                                       $end = get_post_meta( $ticket->ID, 'tix_end', true );
+
+                                                       if ( strtotime( $start ) )
+                                                               if ( strtotime( $start ) < $meta['tix_earliest_start'] || ! $meta['tix_earliest_start'] )
+                                                                       $meta['tix_earliest_start'] = strtotime( $start );
+
+                                                       if ( strtotime( $end ) )
+                                                               if ( strtotime( $end ) > $meta['tix_latest_end'] || ! $meta['tix_latest_end'] )
+                                                                       $meta['tix_latest_end'] = strtotime( $end );
+
+                                                       if ( ! strtotime( $end ) )
+                                                               $meta['tix_undefined_end'] = true;
+
+                                                       if ( strtotime( $ticket->post_date ) < $meta['tix_earliest_start'] || ! $meta['tix_earliest_start'] )
+                                                               $meta['tix_earliest_start'] = strtotime( $ticket->post_date );
+
+                                                       if ( strtotime( $ticket->post_date ) > $meta['tix_latest_end'] || ! $meta['tix_latest_end'] )
+                                                               $meta['tix_latest_end'] = strtotime( $ticket->post_date );
+                                               }
+                                       }
+                                       
+                                       // Set latest end to 1 year from now for better sorting.
+                                       if ( $meta['tix_undefined_end'] || ! $meta['tix_latest_end'] ) {
+                                               $meta['tix_latest_end'] = time() + 60*60*24*356;
+                                               $meta['tix_undefined_end'] = true;
+                                       }
+                                               
+                                       if ( ! $meta['tix_earliest_start'] ) {
+                                               $meta['tix_earliest_start'] = time() + 60*60*24*356;
+                                               $meta['tix_undefined_start'] = true;
+                                       }
+
+                                       // Make note of archived sites
+                                       $meta['tix_archived'] = ( isset( $options['archived'] ) && $options['archived'] ) ? 1 : 0;
+                               }
+
+                       restore_current_blog();
+
+                       if ( $post ) {
+                               $post_id = wp_insert_post( $post );
+                               if ( $post_id )
+                                       foreach ( $meta as $meta_key => $meta_value )
+                                               update_post_meta( $post_id, $meta_key, $meta_value );
+                       }
+               }
+       }
+
+       function init() {
+               register_post_type( 'tix_event', array(
+                       'labels' => array(
+                               'name' => 'Events',
+                               'singular_name' => 'Event',
+                               'add_new' => 'New Event',
+                               'add_new_item' => 'Add New Event',
+                               'edit_item' => 'Edit Event',
+                               'new_item' => 'New Event',
+                               'all_items' => 'Events',
+                               'view_item' => 'View Event',
+                               'search_items' => 'Search Events',
+                               'not_found' => 'No events found',
+                               'not_found_in_trash' => 'No events found in trash',
+                               'menu_name' => 'Events',
+                       ),
+                       'public' => false,
+                       'query_var' => false,
+                       'publicly_queryable' => false,
+                       'show_ui' => true,
+                       'show_in_menu' => $this->debug,
+                       'supports' => array( 'title', 'custom-fields' ),
+               ) );
+       }
+
+       function admin_menu() {
+               $dashboard = add_dashboard_page( 'CampTix Network Dashboard', 'CampTix', 'manage_network', 'camptix-dashboard', array( $this, 'render_dashboard' ) );
+               add_action( 'load-' . $dashboard, array( $this, 'pre_render_dashboard' ) );
+       }
+
+       function pre_render_dashboard() {
+
+               if ( 'overview' == $this->get_current_tab() ) {
+                       $this->init_list_tables();
+                       $this->list_table = new CampTix_Network_Dashboard_List_Table();
+               }
+
+               if ( 'log' == $this->get_current_tab() ) {
+                       $this->init_list_tables();
+                       $this->list_table = new CampTix_Network_Log_List_Table();
+               }
+               
+               if ( 'attendees' == $this->get_current_tab() ) {
+                       $this->init_list_tables();
+                       $this->list_table = new CampTix_Network_Attendees_List_Table();
+               }
+       }
+
+       function get_current_tab() {
+               if ( isset( $_REQUEST['tix_section'] ) )
+                       return strtolower( $_REQUEST['tix_section'] );
+
+               return 'overview';
+       }
+
+       /**
+        * Tabs for Tickets > Tools, outputs the markup.
+        */
+       function render_dashboard_tabs() {
+               $current_section = $this->get_current_tab();
+               $sections = array(
+                       'overview' => 'Overview',
+                       'log' => 'Network Log',
+                       'txn_lookup' => 'Transactions',
+                       'attendees' => 'Attendees',
+               );
+
+               foreach ( $sections as $section_key => $section_caption ) {
+                       $active = $current_section === $section_key ? 'nav-tab-active' : '';
+                       $url = add_query_arg( array(
+                               'tix_section' => $section_key,
+                               'page' => 'camptix-dashboard',
+                       ), network_admin_url( 'index.php' ) );
+                       echo '<a class="nav-tab ' . $active . '" href="' . esc_url( $url ) . '">' . esc_html( $section_caption ) . '</a>';
+               }
+       }
+
+       function render_dashboard() {
+               ?>
+               <div class="wrap">
+                       <h1>CampTix Network Dashboard</h1>
+                       <?php settings_errors(); ?>
+                       <h3 class="nav-tab-wrapper"><?php $this->render_dashboard_tabs(); ?></h3>
+                       <div id="tix">
+                       <?php
+                               $section = $this->get_current_tab();
+                               if ( $section == 'overview' )
+                                       $this->render_dashboard_overview();
+                               if ( $section == 'log' )
+                                       $this->render_dashboard_log();
+                               if ( $section == 'txn_lookup' )
+                                       $this->render_dashboard_txn_lookup();
+                               if ( $section == 'attendees' )
+                                       $this->render_dashboard_attendees();
+                       ?>
+                       </div>
+               </div>
+               <?php
+       }
+
+       function init_list_tables() {
+               require_once ( plugin_dir_path( __FILE__ ) . 'includes/class-camptix-network-dashboard-list-table.php' );
+               require_once ( plugin_dir_path( __FILE__ ) . 'includes/class-camptix-network-log-list-table.php' );
+               require_once ( plugin_dir_path( __FILE__ ) . 'includes/class-camptix-network-attendees-list-table.php' );
+       }
+
+       function render_dashboard_overview() {
+               $last_updated = date( 'Y-m-d H:i:s', get_option( 'camptix_dashboard_timestamp', 0 ) );
+               $last_updated_ago = human_time_diff( get_option( 'camptix_dashboard_timestamp', 0 ), time() ) . ' ago';
+               $this->list_table->prepare_items();
+               ?>
+               <style>
+               #tix_event {
+                       width: 25%;
+               }
+               .dashboard_page_camptix-dashboard td {
+                       padding: 8px;
+               }
+               .tix-tooltip {
+                       cursor: pointer;
+               }
+               </style>
+               <?php /* $this->list_table->views(); */ ?>
+               <form id="posts-filter" action="" method="get">
+                       <input type="hidden" name="page" value="camptix-dashboard" />
+                       <input type="hidden" name="tix_section" value="overview" />
+                       <?php wp_nonce_field( 'dashboard_overview_search_events', 'dashboard_overview_search_events_nonce' ); ?>
+
+                       <?php $this->list_table->search_box( 'Search Events', 'events' ); ?>
+                       <?php $this->list_table->display(); ?>
+               </form>
+               <p class="description">Please note that the network report is cached and updated once every hour. Last updated: <acronym class="tix-tooltip" title="<?php echo esc_attr( $last_updated_ago ); ?>"><?php echo esc_html( $last_updated ); ?></acronym>.</p>
+               <?php
+       }
+
+       function render_dashboard_log() {
+               $this->list_table->prepare_items();
+               ?>
+               <style>
+               #tix_timestamp {
+                       width: 120px;
+               }
+               #tix_message {
+                       width: 60%;
+               }
+               .tix-tooltip {
+                       cursor: pointer;
+               }
+               .tix-network-log-actions::before {
+                       content: 'ยง ';
+               }
+               .tix-network-log-actions,
+               .tix-network-log-actions a {
+                       color: #aaa;
+               }
+               .tix-network-log-actions a:hover,
+               .tix-network-log-actions a:active,
+               .tix-network-log-actions a:focus {
+                       color: #D54E21;
+               }
+               </style>
+               <form id="posts-filter" action="" method="get">
+                       <input type="hidden" name="page" value="camptix-dashboard" />
+                       <input type="hidden" name="tix_section" value="log" />
+
+                       <?php $this->list_table->search_box( 'Search Logs', 'logs' ); ?>
+                       <?php $this->list_table->display(); ?>
+               </form>
+               <script>
+               jQuery('.tix-more-bytes').click(function() {
+                       jQuery(this).parents('.tix_message').find('.tix-bytes').toggle();
+                       return false;
+               });
+               </script>
+               <?php
+       }
+       
+       function render_dashboard_txn_lookup() {
+               $txn_id = isset( $_POST['tix_txn_id'] ) ? $_POST['tix_txn_id'] : false;
+               $creds = isset( $_POST['tix_dashboard_credentials'] ) ? $_POST['tix_dashboard_credentials'] : false;
+               $available_credentials = $this->get_paypal_credentials();
+               ?>
+
+               <?php if ( $available_credentials ) : ?>
+                       <form method="POST">
+                               <input type="hidden" name="tix_dashboard_txn_lookup_submit" value="1" />
+                               <?php wp_nonce_field( 'dashboard_transactions_id_lookup', 'dashboard_transactions_id_lookup_nonce' ); ?>
+
+                               <select name="tix_dashboard_credentials">
+                                       <?php foreach ( $available_credentials as $key => $value ) : ?>
+                                               <option <?php selected( $creds, $key ); ?> value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $value['label'] ); ?></option>
+                                       <?php endforeach; ?>
+                               </select>
+
+                               <input type="text" name="tix_txn_id" placeholder="Transaction ID" autocomplete="off" value="<?php echo esc_attr( $txn_id ); ?>" />
+                               <input type="submit" value="Lookup" class="button-primary" />
+                       </form>
+               <?php else : ?>
+                       No payment gateway credentials were found. Please see <a href="<?php echo esc_url( CampTix_Network_Tools::PLUGIN_URL ); ?>/faq/">the FAQ</a> for details on setting up credentials.
+               <?php endif; ?>
+
+               <?php
+               $txn = false;
+               if ( isset( $_POST['tix_dashboard_txn_lookup_submit'] ) && $txn_id && $creds ) {
+                       check_admin_referer( 'dashboard_transactions_id_lookup', 'dashboard_transactions_id_lookup_nonce' );
+                       $credentials = $this->get_paypal_credentials();
+                       if ( ! isset( $credentials[$_POST['tix_dashboard_credentials']] ) )
+                               return;
+                               
+                       $credentials = $credentials[$_POST['tix_dashboard_credentials']];
+
+                       $payload = array(
+                               'METHOD' => 'GetTransactionDetails',
+                               'TRANSACTIONID' => $txn_id,
+                       );
+
+                       $txn = wp_parse_args( wp_remote_retrieve_body( $this->paypal_request( $payload, $credentials ) ) );
+               }
+               
+               ?>
+               <?php if ( $txn ) : ?>
+                       <style>
+                       #tix-dashboard-txn-info {
+                               padding: 20px;
+                               background: #F5EFC6;
+                       }
+                       </style>
+                       <pre id="tix-dashboard-txn-info"><?php 
+                               echo esc_html( print_r( $txn, true ) );
+                       ?></pre>
+               <?php endif; ?>
+               <?php
+       }
+       
+       function render_dashboard_attendees() {
+               $search_query = isset( $_POST['s'] ) ? $_POST['s'] : '';
+               if ( isset( $_POST['tix_dashboard_attendee_lookup_submit'], $_POST['s'] ) )
+                       $this->list_table->prepare_items();
+               ?>
+               <form method="POST">
+                       <label class="description">Search Query:</label>
+                       <input type="hidden" name="tix_dashboard_attendee_lookup_submit" value="1" />
+                       <input type="text" name="s" placeholder="Name, e-mail, twitter, URL, ..." value="<?php echo esc_attr( $search_query ); ?>" />
+                       <input type="submit" value="Lookup" class="button-primary" />
+                       <?php wp_nonce_field( 'dashboard_attendees_search_query', 'dashboard_attendees_search_query_nonce' ); ?>
+               </form>
+               
+               <?php if ( isset( $_POST['tix_dashboard_attendee_lookup_submit'], $_POST['s'] ) ) : ?>
+                       <style>
+                       #tix-dashboard-attendees-table {
+                               margin-top: 20px;
+                       }
+                       #tix-dashboard-attendees-table .tablenav {
+                               display: none;
+                       }
+                       </style>
+                       <div id="tix-dashboard-attendees-table">
+                               <?php if ( count( $this->list_table->items ) >= $this->list_table->max_results ) : ?>
+                                       <p class="description">Please note, that for performance reasons, we don't show more than <?php echo absint( $this->list_table->max_results ); ?> results.</p>
+                               <?php endif; ?>
+                               <?php $this->list_table->display(); ?>
+                       </div>
+               <?php endif; ?>
+               <?php
+       }
+       
+       function paypal_request( $payload, $credentials ) {
+               $url = $credentials['sandbox'] ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp';
+               $payload = array_merge( array(
+                       'USER' => $credentials['api_username'],
+                       'PWD' => $credentials['api_password'],
+                       'SIGNATURE' => $credentials['api_signature'],
+                       'VERSION' => '88.0', // https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_nvp_PreviousAPIVersionsNVP
+               ), (array) $payload );
+
+               return wp_remote_post( $url, array( 'body' => $payload, 'timeout' => 20 ) );
+       }
+       
+       function get_paypal_credentials() {
+               return apply_filters( 'camptix_dashboard_paypal_credentials', array() );
+       }
+}
+
+$GLOBALS['camptix_network_dashboard'] = new CampTix_Network_Dashboard;
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsreadmetxt"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/readme.txt</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/readme.txt                          (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/readme.txt    2018-05-22 22:57:38 UTC (rev 7217)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,121 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+=== CampTix Network Tools ===
+Contributors:      automattic, kovshenin, iandunn
+Tags:              CampTix, ticketing, event ticketing, multisite, log
+Requires at least: 3.5
+Tested up to:     4.3
+Stable tag:        0.2
+License:           GPLv2 or later
+License URI:       http://www.gnu.org/licenses/gpl-2.0.html
+
+Tools for managing CampTix installations across a WordPress Multisite network.
+
+
+== Description ==
+
+**Features**
+
+* Network Dashboard
+       * Overview of CampTix statistics for each site in the network
+       * View and search log entries
+       * Lookup transactions details
+       * Lookup attendee details
+* Log events are captured and stored in a global database table instead of individual site postmeta tables
+* Receive e-mail notifications when log events match your custom patterns
+
+*Note: This plugin requires a [WordPress Multisite](http://codex.wordpress.org/Glossary#Multisite) installation in order to work.*
+
+Feel free to post your feature requests, issues and pull requests to [CampTix Network Tools on GitHub](https://github.com/automattic/camptix-network-tools "CampTix on GitHub").
+
+
+== Installation ==
+
+***Note: In order for this plugin to work, you must have a [WordPress Multisite](http://codex.wordpress.org/Glossary#Multisite) installation already setup, and have a Super Admin account.***
+
+1. Download and extract CampTix Network Tools in your `wp-content/plugins` directory, or search for it in the Plugins page in WordPress.
+1. Navigate to the Plugins page in the Network Admin area of WordPress.
+1. Network Activate the plugin.
+
+You will then be able to the view the CampTix page under the Dashboard menu of any of your sites.
+
+
+== Frequently Asked Questions ==
+
+= Where is the CampTix Network Dashboard located? =
+The dashboard is located under the Dashboard menu in the Network Admin area (e.g., http://example.org/wp-admin/network/).
+
+= Why don't my sites show up in the Overview tab? =
+The data on the Overview tab is only generated once every hour. You can tell when it was last generated by looking at the bottom of the page.
+
+= Why do I get error on the Transactions tab saying credentials weren't found? =
+
+In order to lookup transaction details, CampTix Network Tools needs to know what payment gateway to use, and what your credentials are for it.
+
+You can specify them by setting up a filter callback like the example below. The best place to put the code is inside a [functionality plugin](http://www.doitwithwp.com/create-functions-plugin/).
+
+Currently, transaction lookups are only available with PayPal.
+
+`function camptix_dashboard_paypal_credentials( $credentials ) {
+       $credentials = array(
+               "sandbox-account" => array(
+                       'label'         => "Sandbox Account",
+                       'sandbox'       => true,
+                       'api_username'  => '',
+                       'api_password'  => '',
+                       'api_signature' => '',
+               ),
+               "production-account" => array(
+                       'label'         => 'Production Account',
+                       'sandbox'       => false,
+                       'api_username'  => '',
+                       'api_password'  => '',
+                       'api_signature' => '',
+               ),
+       );
+       return $credentials;
+}
+add_filter( 'camptix_dashboard_paypal_credentials', 'camptix_dashboard_paypal_credentials' );`
+
+= How do I get e-mail notifications when log events occur? =
+You can use the `camptix_nt_notification_expressions` filter to add custom notifications. For each entry, you'll provide a regular expression that matches a log entry, and an array of e-mail addresses that will be notified whenever a match occurs. The best place to put the code is inside a [functionality plugin](http://www.doitwithwp.com/create-functions-plugin/).
+
+Here's an example of several different patterns being matched and associated with e-mail addresses:
+
+`function camptix_email_notification_expressions( $expressions ) {
+       $expressions = array_merge( $expressions, array(
+               '/changed to (failed|pending|refund)/'     => array( 'jane@example.org', 'admin@example.net' ),
+               '/Error during RefundTransaction/i'        => array( 'admin@example.net' ),
+               '/Setting all transactions to refund/i'    => array( 'jane@example.org' ),
+               '/Warning during PayPal request/i'         => array( 'admin@example.net', 'jane@example.org' ),
+       ) );
+
+       return $expressions;
+}
+add_filter( 'camptix_nt_notification_expressions', 'camptix_email_notification_expressions' );`
+
+For help understanding regular expressions, check out <a href="http://www.marksanborn.net/howto/learning-regular-expressions-for-beginners-the-basics/">Learning Regular Expressions for Beginners</a> and <a href="http://www.zytrax.com/tech/web/regex.htm">Regular Expressions User Guide</a>. You can use <a href="http://gskinner.com/RegExr/">RegExr</a> to test your expressions.
+
+
+== Screenshots ==
+
+1. Overview of ticket sales and related data across all CampTix installations
+1. Log entries for all transactions and other events
+1. Lookup payment transaction details
+1. Lookup attendee details
+
+
+== Changelog ==
+
+= 0.2 (2015-04-20) =
+* Security: Escape links in wp-admin.
+
+= 0.1 (2013-06-18) =
+* Initial release
+
+
+== Upgrade Notice ==
+
+= 0.2 =
+This is a security release. Please update immediately.
+
+= 0.1 =
+CampTix Network Tools v0.1 includes a network dashboard, searchable log entries, transaction and attendee records, and e-mail notifications for custom log patterns.
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot1png"></a>
<div class="binary"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-1.png</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx" style="display: block; padding: 0 10px">Index: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-1.png
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-1.png       2018-05-22 05:58:57 UTC (rev 7216)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-1.png        2018-05-22 22:57:38 UTC (rev 7217)
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-1.png
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:mime-type</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+application/octet-stream
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot2png"></a>
<div class="binary"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-2.png</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx" style="display: block; padding: 0 10px">Index: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-2.png
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-2.png       2018-05-22 05:58:57 UTC (rev 7216)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-2.png        2018-05-22 22:57:38 UTC (rev 7217)
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-2.png
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:mime-type</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+application/octet-stream
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot3png"></a>
<div class="binary"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-3.png</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx" style="display: block; padding: 0 10px">Index: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-3.png
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-3.png       2018-05-22 05:58:57 UTC (rev 7216)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-3.png        2018-05-22 22:57:38 UTC (rev 7217)
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-3.png
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:mime-type</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+application/octet-stream
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixnetworktoolsscreenshot4png"></a>
<div class="binary"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-4.png</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx" style="display: block; padding: 0 10px">Index: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-4.png
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-4.png       2018-05-22 05:58:57 UTC (rev 7216)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-4.png        2018-05-22 22:57:38 UTC (rev 7217)
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-network-tools/screenshot-4.png
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:mime-type</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+application/octet-stream
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>