From c1e4c31f199d851f1f7626198d9d00ad0399e4db 2017-06-12 18:57:18
From: DarkMorford \n';
+ }
+ else {
+ result += '
\n';
+ }
+
+ // Code for QUARTER display
+ // ------------------------
+ if (this.displayType=="quarter") {
+ result += '\n';
+ result += '
\n';
+ }
+
+ // Code common for MONTH, QUARTER, YEAR
+ // ------------------------------------
+ if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") {
+ if (arguments.length > 0) { var year = arguments[0]; }
+ else {
+ if (this.displayType=="year") { var year = now.getFullYear()-this.yearSelectStartOffset; }
+ else { var year = now.getFullYear(); }
+ }
+ if (this.displayType!="year" && this.isShowYearNavigation) {
+ result += "\n';
+ result += ' ";
+ }
+ result += '
\n';
+ result += '\n';
+ var refresh = windowref+'CP_refreshCalendar';
+ var refreshLink = 'javascript:' + refresh;
+ if (this.isShowNavigationDropdowns) {
+ result += ' ';
+ result += ' ';
+
+ result += '';
+ }
+ else {
+ if (this.isShowYearNavigation) {
+ result += ' < ';
+ result += '';
+ result += ' > ';
+ result += ' ';
+
+ result += '< ';
+ if (this.isShowYearNavigationInput) {
+ result += '';
+ }
+ else {
+ result += ' ';
+ }
+ result += ' > ';
+ }
+ else {
+ result += '<< \n';
+ result += '\n';
+ result += ' >> \n';
+ }
+ }
+ result += '\n';
+ result += '
\n';
+ for (var j=0; j<7; j++) {
+
+ result += ' \n';
+ for (var row=1; row<=6; row++) {
+ result += ''+this.dayHeaders[(this.weekStartDay+j)%7]+' \n';
+ }
+ result += '\n';
+ for (var col=1; col<=7; col++) {
+ var disabled=false;
+ if (this.disabledDatesExpression!="") {
+ var ds=""+display_year+LZ(display_month)+LZ(display_date);
+ eval("disabled=("+this.disabledDatesExpression+")");
+ }
+ var dateClass = "";
+ if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) {
+ dateClass = "cpCurrentDate";
+ }
+ else if (display_month == month) {
+ dateClass = "cpCurrentMonthDate";
+ }
+ else {
+ dateClass = "cpOtherMonthDate";
+ }
+ if (disabled || this.disabledWeekDays[col-1]) {
+ result += ' ';
+ }
+ var current_weekday = now.getDay() - this.weekStartDay;
+ if (current_weekday < 0) {
+ current_weekday += 7;
+ }
+ result += ''+display_date+' \n';
+ }
+ else {
+ var selected_date = display_date;
+ var selected_month = display_month;
+ var selected_year = display_year;
+ if (this.displayType=="week-end") {
+ var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0);
+ d.setDate(d.getDate() + (7-col));
+ selected_year = d.getYear();
+ if (selected_year < 1000) { selected_year += 1900; }
+ selected_month = d.getMonth()+1;
+ selected_date = d.getDate();
+ }
+ result += ' '+display_date+' \n';
+ }
+ display_date++;
+ if (display_date > daysinmonth[display_month]) {
+ display_date=1;
+ display_month++;
+ }
+ if (display_month > 12) {
+ display_month=1;
+ display_year++;
+ }
+ }
+ result += '\n';
+ result += ' \n';
+ if (this.disabledDatesExpression!="") {
+ var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate());
+ eval("disabled=("+this.disabledDatesExpression+")");
+ }
+ if (disabled || this.disabledWeekDays[current_weekday+1]) {
+ result += ' '+this.todayText+'\n';
+ }
+ else {
+ result += ' '+this.todayText+'\n';
+ }
+ result += '
\n';
+ result += ' ";
+ result += '
\n';
+ }
+ }
+
+ // Code for MONTH display
+ // ----------------------
+ if (this.displayType=="month") {
+ // If POPUP, write entire HTML document
+ result += '\n';
+ result += ' << \n';
+ result += ' '+year+' \n';
+ result += ' >> \n';
+ result += '\n';
+ for (var i=0; i<4; i++) {
+ result += '
';
+ for (var j=0; j<3; j++) {
+ var monthindex = ((i*3)+j);
+ result += ' ';
+ }
+ result += ''+this.monthAbbreviations[monthindex]+' ';
+ }
+ result += '\n';
+ for (var i=0; i<2; i++) {
+ result += '
';
+ for (var j=0; j<2; j++) {
+ var quarter = ((i*2)+j+1);
+ result += ' ';
+ }
+ result += ' ';
+ }
+ result += '
Q'+quarter+'";
+ result += '
\n';
+ result += '\n';
+ result += ' << \n';
+ result += ' >> \n';
+ result += '\n';
+ for (var i=0; i
\n';
+ }
+ // Common
+ if (this.type == "WINDOW") {
+ result += "\n";
+ }
+ return result;
+ }
diff --git a/character-twitter.php b/character-twitter.php
new file mode 100644
index 0000000..ed255a0
--- /dev/null
+++ b/character-twitter.php
@@ -0,0 +1,122 @@
+getRow(sprintf("SELECT username, password FROM twitter_user WHERE id = '%d'", $_REQUEST['twitter-account']));
+
+ $post_at = strtotime($_REQUEST['date18']);
+
+ if($post_at)
+ {
+ if($post_at <= strtotime('now'))
+ {
+ #If we can post immediately, do so. Bypass the scheduler whenever possible.
+ #Treat a date/time in the past as immediate.
+ $ret = twitterpost($_REQUEST['message'], $acct->username, $acct->password);
+
+ if($ret)
+ {
+ $info.='Update posted to Twitter. View Twitter.';
+ adminlog('New manual post to Twitter for user '. $acct->username .'.', MTS_TWITTER, MTA_ADD);
+ }
+ else
+ {
+ $error.='There was an error posting to Twitter.';
+ }
+ }
+ else
+ {
+ #No luck, gotta schedule.
+ $mtdb->query(
+ sprintf("INSERT INTO twitter_post (status, user, time, text)VALUES ('scheduled', '%d', FROM_UNIXTIME('%d'), '%s')",
+ mysql_real_escape_string($_REQUEST['twitter-account']),
+ $post_at,
+ mysql_real_escape_string($_REQUEST['message'])
+ )
+ );
+ $info .= "Your tweet for user " . htmlentities($acct->username) . " has been scheduled.";
+ adminlog('Tweet for account ' . $acct->username . ' has been scheduled.', MTS_TWITTER, MTA_ADD);
+ }
+ }
+ else
+ {
+ $error .= 'Could not make sense of your designated time/date. Please try again.';
+ }
+}
+
+$characters = $mtdb->getAll("SELECT id, username FROM twitter_user ORDER BY username");
+
+$scheduled = $mtdb->getAll("SELECT username, text, status, twitter_post.id AS id, time
+ FROM twitter_post JOIN twitter_user
+ ON twitter_post.user = twitter_user.id
+ WHERE twitter_post.status = 'scheduled' ORDER BY time");
+
+adminhead('Manage Character Twitters');
+adminmenu();
+?>
+
+Manage Character Twitters
+
| User | +Tweet | +Time | ++ |
|---|---|---|---|
| username; ?> | +text; ?> | +time); ?> | +Delete | +
Comic posted!
"; + break; + + case 'edit_comic': + if( 0 >= $strip->new_id ) mtdie('Strip numbers must be numeric, greater than 0.'); + if( 0 >= $strip->id ) mtdie('Existing strip number, in the form, was zero. This should never happen.'); + + // When updating, $strip->id is the old strip number. Update in place first. Possibly adjust strip number later. + check_nonce('save-strip-'.$strip->id); + + // If uploading, get filetype. If not uploading, filetype got loaded from database earlier. + if( is_valid_upload('comicFile') ) { + $imagedata = getimagesize($_FILES['comicFile']['tmp_name']); + $strip->media = $imagedata[2]; + } + $fileext = $mtdb->getOne( 'SELECT extension FROM media_t WHERE id=' . (int)$strip->media ); + + if(strlen($fileext) < 3) + { + //bad image upload type + adminlog("Bad image type upload on strip ".$strip->id.". Invalid media type.", MTS_STRIP, MTA_UPDATE, E_ERROR); + mtdie('Bad image type upload on strip '.$strip->id.'. Invalid media type.'); + } + + // Update existing strip + if(!updatestrip( $strip ) ) + { + adminlog("Failed to update strip ".$strip->id.".", MTS_STRIP, MTA_UPDATE); + mtdie('Error updating strip: ' . mysql_error(), 'SQL Error'); + } + + if( is_valid_upload('comicFile') ) { // If uploading, store the uploaded file to xxxx-n.ext + $basefile = $strip->published <= time() ? + sprintf(SITE_PATH_ABS.'/'.SITE_STRIP.'/'.'%04d.%s', $strip->id, $fileext) : + sprintf(SITE_PATH_ABS.'/'.SITE_STRIP.'/restricted/'.'%04d.%s', $strip->id, $fileext); + + if(!move_uploaded_file($_FILES['comicFile']['tmp_name'], $basefile)) + { + adminlog("Filesystem error in saving image.", MTS_STRIP, MTA_UPDATE, E_ERROR); + mtdie("There was a problem storing the uploaded strip image. PleaseUpdate broadcasted with message '$b_msg'.
"; + } + + $info.="Changes saved. id."\">View on site."; + break; + + default: + adminlog("User did something strange.", MTS_STRIP, MTA_MODIFY); + mtdie('You know, it would be really nice if you avoided nonsensical actions.'); + } + + $info = savetranscript($strip) . $info; + + # If the strip number changed, swap strips sequentially to shuffle it into place + if( $strip->id != $strip->new_id ) { + $f = fopen(SITE_PATH_ABS.'/'.SITE_STRIP.'/'.SITE_STRIP_LOCK, 'w'); + flock($f, LOCK_EX); + + while( $strip->new_id < $strip->id ) { // Move this strip backward + swap_strips( $strip->id - 1, $strip->id ); + $strip->id--; + } + while( $strip->id < $strip->new_id ) { // Move this strip forward + swap_strips( $strip->id + 1, $strip->id ); + $strip->id++; + } + $strip->id = $strip->new_id; + close($f); + } +} + +////////////////////// Display Edit Form //////////////////////// +$strip = getstrip($strip->id); +gettranscript($strip); + +adminhead('Edit Comic'); +adminmenu('manage-comics.php'); + +?> + +
' . ( $page->status === 'draft' ? 'Page draft saved.' : sprintf('Page published. View on site.', SITE_HOST, SITE_PATH, $page->url_name) ) . '
'; + +} elseif( !$page->url_name ) { + mtdie('Attempted to edit page with no name supplied.', 'Bad Request'); +} + +/////////////////////// Display Edit Form /////////////////////// + + +adminhead('Edit Page'); +adminmenu('manage-pages.php'); + + +?> + + + + + +Attachment $i was not uploaded properly
"; + $source_rantattachment_filename[] = ''; + } else { + $source_rantattachment_filename[] = $_FILES['rant_attachment']['tmp_name'][$i]; + } + } + + switch( $_POST['action'] ) { + case 'new_rant': + + check_nonce('new-rant'); + + if( ! $doing_upload ) { + // Use default rant image for this contributor. + $contributor = get_userdatabyid( $rant->author ); + $image_data = getimagesize(SITE_PATH_ABS .'/'. SITE_RANT .'/'. $contributor->default_image); + $rant->imagetype = $image_data[2] ? $image_data[2] : 'NULL'; + $source_rantimage_filename = $contributor->default_image; + } else { + $rant->imagetype = $upload_imagetype; + } + + $rant->id = insertrant($rant); + if( $rant->id === false ) + { + adminlog("Error on rant insertion: ".mysql_error(), MTS_RANT, MTA_INSERT, E_ERROR); + mtdie('There was an error inserting the rant into the database.', 'SQL Error'); + } + + for($i = 0; $i < count($source_rantattachment_filename); $i++) { + if('' == $source_rantattachment_filename[$i]) continue; + + $upload_error = $upload_info = ''; + extract( save_upload_rant_attachment($_FILES['rant_attachment']['tmp_name'][$i], $rant->id) ); + + if( $upload_error ) $error.=$upload_error; + if( $upload_info ) { + $info .= $upload_info; + $rant->body = preg_replace('/(href|src)=\"([^\"]*?)\\{'.($i+1).'\\}(.*?)\"/', '\1="'.get_rantattachment_filename($rant_attachment_id).'"', $rant->body); + } + } + + if(count($_FILES['rant_attachment']['error']) > 0) + updaterant($rant); + + if( $doing_upload ) { + extract( save_upload_rant_image( $source_rantimage_filename, $rant ) ); + if( $upload_info ) $info.=$upload_info; + if( $upload_error ) $error.=$upload_error; + } elseif($rant->imagetype != 'NULL') { + extract( save_stock_rant_image( $source_rantimage_filename, $rant ) ); + if( $upload_info ) $info.=$upload_info; + if( $upload_error ) $error.=$upload_error; + } + + break; + + case 'saverant': + + check_nonce('save-rant-' . $rant->id); + + if( isset( $_POST['rant_reverttodefaultimage'] ) ) { + // Use default rant image for this contributor, copy it into place + $contributor = get_userdatabyid( $rant->author ); + $imagedata = getimagesize(SITE_PATH_ABS .'/'. SITE_RANT .'/'. $contributor->default_image); + $rant->imagetype = $imagedata[2] ? $imagedata[2] : 'NULL'; + if($rant->imagetype != 'NULL') + extract( save_stock_rant_image( $contributor->default_image, $rant ) ); + adminlog("Reverting to user's default rant image for rant ".$rant->id.".", MTS_RANT, MTA_UPDATE); + + } elseif( $doing_upload ) { + $rant->imagetype = $upload_imagetype; + extract( save_upload_rant_image( $source_rantimage_filename, $rant ) ); + adminlog("Uploading new rant image for rant ".$rant->id.".", MTS_RANT, MTA_UPDATE); + if( $upload_info ) $info.=$upload_info; + if( $upload_error ) $error.=$upload_error; + } + + foreach($_POST['delete_attachment'] as $attachment) + deleteattachment($attachment); + + $existing_attachments = $mtdb->getAll('SELECT ra.id AS id, extension FROM rant_attachment ra JOIN media_t ON ra.media = media_t.id WHERE ra.rant = '.$rant->id.' ORDER BY id'); + for($i = 0; $i < count($existing_attachments); $i++) { + $rant->body = preg_replace('/(href|src)=\"([^\"]*?)\\{'.($i+1).'\\}(.*?)\"/', '\1="'.get_rantattachment_filename($existing_attachments[$i]->id).'"', $rant->body); + } + + for($j = $i; $j < count($source_rantattachment_filename) + $i; $j++) { + if('' == $source_rantattachment_filename[$j - $i]) continue; + + $upload_error = $upload_info = ''; + extract( save_upload_rant_attachment($_FILES['rant_attachment']['tmp_name'][$j - $i], $rant->id) ); + + if( $upload_error ) $error.=$upload_error; + if( $upload_info ) { + $info .= $upload_info; + $rant->body = preg_replace('/(href|src)=\"([^\"]*?)\\{'.($i+1).'\\}(.*?)\"/', '\1="'.get_rantattachment_filename($rant_attachment_id).'"', $rant->body); + } + } + + updaterant($rant); + + if($rant->status != 'draft' && $_POST['broadcast'] && + ($rant->published <= mktime(0,0,0, date('m'), date('d')-1, date('Y')))) + { + rsspost('Rant '.$rant->id.' updated.', SITE_HOST.SITE_PATH.'/rant/'.$rant->id); + twitterpost('Rant '.$rant->id.' updated: '.SITE_HOST.SITE_PATH.'/rant/'.$rant->id); + } + + break; + } + if( $error ) $action='edit'; + + if( 'post' == $action ) _redirect( ADMIN_PATH . '/manage-rants.php?saved=success' ); + + $info.= '' . ( $rant->status === 'draft' ? 'Rant draft saved.' : sprintf('Rant published. View on site.', SITE_HOST, SITE_PATH, $rant->id) ) . '
'; + +} elseif( !$rant->id ) { + mtdie('Attempted to edit rant with no rantid supplied.', 'Bad Request'); +} + +/////////////////////// Display Edit Form /////////////////////// + + +adminhead('Edit Rant'); +adminmenu('manage-rants.php'); + +?> + + + + +@70?M+Kd z%gD$mE-t=yZXe+}AwE6<-+))<+jNQp*0ikQ^ZAkekpTe#34;lZjg5=Ti+{fTv$L~P z@ImnC+aqP`#T)!nZEbDGdydOwvewqt`ic6H7a^~Hyh7KbBSVkw_;*J)i&a}&>(=Qe z7!XV?P6dAmp7=Gvlk<9JdX_5efCOClc)?I=nDiydtksO%NPhV6q1Fqnfw=*ncRsgb z&nO?CkBJDqapT6{fB${?(`CEYc6HzD8Was)-CiwIEy`57;+JB4VLUQ2vZ<*leJH)5 zp&>XpxVpO9rrpM=!$}|z45l)b9U&JkT&S6-@rm?lnQqC;%cHl`e{cK!%9SgPipD2T zo-k$118)wjd$~>|5@ii%DI0>lySeSJ9M}q@Sq}|O8<44ZU&GIkvuUrn5&&n|^n9 7W+nw+~HZe0%CN8`6>6S9v zx1g|ad~6&vfy&{^<6Xyz&xxO=K2=p!H8(fsM(=O^w^jN>ig<=lJ`X*5^r*kD|G+?h z;_bVSsvoB&rzXl1+rG6as}Jt_cUR?5<;IsATUuIrM|x#HWKn`^+|$ROPCorM|1B{k zLz&Gy^Y)C^b1l19cCTW#|EvvCR-Bugn|uBG_0rPP$jHdsw{MmIkKsIo06+;W!GHYU zCjd?fl=#K&2cr52oQ8#$RGjrZhGguPp2C$&!pkb}b^hbJ+U4d-`#6HDdTvX+se86p zQTegf+m=4H*IWaS>z^-!59gM}p>v%78%xD@zgb7lA}#7&?2COJ1*|On42y>+yGr(# z9emY#zT}VlOZvYbDT{Nq=Py~?s%u$zZ*%^;|LJx2&7X?d=?ou>H$|m=s_euOy_1 z Ge~dlF##TjGMu za6+xl2g`OQ{R0?Ll~AHocg5%<%r$d~Yv;$ei|mb)htmbi*Bso|6bKz!%}aUV1I7g* z<$^V7VkBN*IwSpjbHjq};LQpGO=7I+5aJu@tWM`8I3Y(~-_kUy$+dU-&DA8C43E6t zc1Mx3n^~#2^*T0@emmd()T5%~rU~~ 79sL*Ws*a zeOpx22{i^jYQv76;oNqo(VKZ^tcpkSlH4pt@}svU5bY~Nq_6#!=>zG-MWv%BOYX9j zNc@`AwptuDUpyL1P4Ul KxZI3$7T+q0Y{y1H@sTYi z54#TP7d`6Lf4qC!gKIrEx8+4v=h%;JL&C;cmQ@qAh2x`f)U;}cmb>lDy@GwewW?Fh zi+NUOpW!O*DC*(OZHgNx%VGr(vOOmoNnt9J&$U<7Ok`{BHV=3~-hDwFdqmqeDs{|l zz{@Oazk?g~(z>B?`-Ia8Eyk(7 O52pKlod0?^My)nJ`{q?a z&4T3NYuWN?YMe>HV|w6|dqWYe#*3Ea(xMFEz)kZU-T=-dE&W`aWp?!#ajb1i_u^Fh zmTm_k+3xJ+X_;TxlbLsCzh9n}e>Zk-UCQN9$C5q6GtI*ij9Txd)@2u)EY~#%*s~D> zEwbyC4lTxs!#(DdEd2iQh%3ts5i+Af0$GuCwkl94(_sm0tq9j_6rndr>s3QZ{3KnE znD!SpS%ZG+w_+jFjXnaFqk|@EL%OkZJbsvCv_4%9QSE&2`zpi9H7HKM(jXpowQOGP zvLib%F}BfU0ToX6&(=(3IhpS2i1Vn+)~=Ag2-t)?f4r60?k|bmIfspgx*Vx9O>z1M z!nN*jA`P~7!0Ok-SI_ bv@W7zeYt+2ooPXsT|i)S-MTNqp@KWhK6ebl(mC>O!RH zE`H9YZAbn|=Rg)ydnCIq4QS-YJKA#&G&ysRXl4n&P!5gU^cv3Aj_rDZx+{!XDH#xo z-y!s63LY>h$5n?kpw20pz25QR*d385q^tYB9xD%B5EW=qqr|z46H)>!oh&SvyaTe1 zjdvJMW}7<%e{6IRW{eOlb7Uxuxg1=WKQPdg@XQFEcndiXToLmygFb0bJOTC{2Scx9 z2*(vn4Z4KSVadU}I)n>Z5P^{e?tiG6V@#6>>< P-|Iv0WI z qJZcqae&EaX* zHTc{2I2yb4Uxn{+G%Twfts42N2bAMcC54`UnOK$Kk)mv^i~6Ev8hK^dr6Z!SMF@Fd z4 W&_k>XtLh+(L= d-J2^u|XTivYRb6HRxZ z@jsr)*2)=fgjh7jpN PTGW6&GIAcj& o8vTSC zMBy_(GLSkU;r55Q{3X~5Y@#%*qL82sA%6&0i6S(nUwSL8)eC1dbEw52Iv~Wzu@o>U zEHTkdj8qalMCi5t3O;mNg8uCtg8-L>lY9-erFDiTUwOO+rh{gIzcYG~2|w}R6Ck%2 zdV@mb4w8JuKkt~tcVg^z3={o`&cJ-Nkz#T6*kuxgEwz-I;rKdNqzqkx9 m4fMRShWw8=P2U6ICJQ9WgX97CsMt>{ovowY=JqDZI1 zxAe^fQ`Jo2on8FC`S2zvIyz2GfwFb? Q-}0U!%?n__=to14bgeJXg#(9bE&akuUQshSH}wv zQ5b4ptDd9Q-dA&@hu==qb=WY~fp=$fkogK1v>62+Vsq49>Y+Ag`%BkuG{mw!fsu |;ki(=SILX$>|cJoeQ#yTSyPlX0R1H%8+62~+H>6B)eEkNR=mjPO;k zWYOCUh1Qj$)>)I|&e$VSmeNMBo4~VHY}Z;&0ZMf8QJ(ewZCa~k1@> {j;E0DLN>aLhA-#BCx+lu5rknN z4>6FLY`B|-^X91Sm0@4`5ihY3D>>*p3b;t !wht4@+{596keB4pvIMOs!R#Wj#3Dcg&@w>MNKhE> zm>%tHjRb6!Kzv^nX9i>-IgiIhX9yW-GR$foNDvxe $A~%1 zK|~3W*SM;yX^16?3Zy_#V-e>oVLt{U-c)6ivt@e+&X0t<`ju$T#oh80@PY(l;CmcE z;|e5|=bP1h{-k8D4h@MVp)UZW0b7+o`@>p-IVD7R(S+k%aFT(wW*lA?sSp@grUdd4 zVy#Kg!M6}@&gCZ*y(q^uB;zq+h})E42>vsHJVU|9OW;BU?5X78CP=8?#e_hS+FglG z85iTOK& 2V$n!5wXbn&y$)VjG6(hh} z#l|2M>A^e >B2gz3v1?^O@4&iFW#bmA52a32#NU*#y`!A;Zn zyEJ%7f-j`73rTPu_h4WD{zV1ux&*#SaqjFAMR%na__4ZOqoONS)G0t9@(lA;Sx36K zW6mUAhBMAplm1s57g)(+d_Fm3{TzHpl$j^T7fNtd^V_ch_`1ktLlyttXVJC=mt$-c z*_NeI%-TYNkaCO@Km^Nytq^fic7msQXDUlgHy%#Q(kDO6Bj>?|LRh3Ymm ;73gIkYHSMl57p_Q=arn!}I~&K||zOC{cCfN(#z!p03(833Fs z66=?g+m=+?mY6Wq+Sn!YJT(&@p*FPW3me%*Beru?%NHDOmBD^Yl3hG(Z>TAQ?~CN* zLJ52i#6S$BA;rZ|jQm)OG#A6O+mV)>Tbpg*JP?s3$D4EQI~Gb6`VXdTA50&7FcbPf zVWC~b)_& hssbPeejqI|G02s>2+i6f4 zPmH^&iW8}sGgS1ph^ZIL&F++2JTA9W!c5E8WuFFd-XQzv3Ngb=r}qa}nD+7BAErUM%i zB4QU3$uhbyt)L5qU5Jiq1X9pNcoCyU?EKjIwR6sP&d#WJ|3f^y@5B4@;ro5Q@H`nB z?5Q=JHUI!>J-x1B*z4hG(QDwHrX3h;+9||~0Pyzn<(hc)-8kzWo4q BYQ 0(hN37q3tsZ^3=nJ*Lwf)E4|$8ZwI zDT<==d0Cc}3iQemcvT6{(0zg+Dy#wlumEcHJ|gVUa8cJj}N=OPc@6q?04sqGaLDFY_>&C?V@vg zF%;d4HN}M8Jz)Wj&Fw)JiH}e8#mD;w;?Kt?29}!isT6<;b$VC5dw=t_{_HaPNpI>j zatha>m^(UGdb*-7dZNv}Kx@)i){Rz+#cF-lXk9lhw;oO&)j%|y2q%uh;bbzL{OJoP zlF4Ku0aU4tm%sO?ueYQfH@-Mp(+2g*4*bk~4DF{jr#F5br8W}>Ax&tjT?K{bo%6d+ zjdroy69K`@NM`lYs4tUUUGuH^F8Karvlpvz8_pw$_aV}Toba}tK h($ literal 0 Hc$@ ({UM@87?A_3Hcg?>~S3eE$6T)2C0ref#$H>(?(|zC3yI z )09<~Z`;0O=dMx} z BhC@7%ptCXp>7#5r>v4?ov4cTuVP z?>~I} n2$_|6|3l26f)0=X_;Dvls>6IuEh8@S4+Ii(16z-hZ zxL8I&&a7a=0#AoVF@c|g3k)XCKRw+b`P3ZG&CAZtwkUpe=j7()=kLz9^9`DzHrb(@ zlhuYX_SBY@mzM`D_L^%|>d?rk+i`A<*uh4fRSAc?WWBeoxw({4#zmuC_&_6run0>) z)wXqacUOFVRcfiw(8S5ew`7ecgCheY8_y9Zhw}B0k55qcerA5)L(>r^CWQ;Dgc%x{ zIi(yT6dW6w#a0Bbjyt<+>+9 5J+(lM{o~AY7ata6jp~4Pka(idJ#%=5JqkgL~Ia6Y!5+a z4m@8HO?eMMWe-1N4m(~BJ6#o5i4;?T5=(UvM{f^4Vh=uH4?SQGI$I7oS`9W-4K-92 zS&S4>eiTrB6HIp!NO2B0SPe8%6;y*0OLY=Qa1J+D4Kz^=Gf@^+g%(wV6is*&N^=uQ zat=0C4Khy|UXmAAh89zS7gdB7QGOLpcoa%<7*~cDRDl*yd=^l96-{;*Q-2jpa~)-! z8CZrGRe~5)fF5R@8d-=IOmrAgdlyf57fyE 1Pk0<#i6Lj799f1P zT8A81gc?zLBWa)>T#6i3e;-_l98-KCU5g)EhaOmiAzq6gS%e-{ejitWB43OsY@#Dz zjUrx(Az6YXV2mSOh#^>kAy $f`PkazcbP`Z~5JqheLT40JgcVkW6i<2;QhpUqcNkWK6-;y)S%(={gcnbD z8d!xJS%w-_fErYO8Buv0S%n){f*V$W8Bll~T!|V|dLLbi9$JMRSAiT;dmU7KAYF(a zReT>-ej;9pAzOtaT!tiGh$dr>C0~grVvQ$ZizQryCS8RpWsoaokt$@4C}4;yWsfRi ziY#T0Dqx2#W{)jpjVxn|E@X=@WsEayl`v+FGHH-AYLPZ=l{;>ePk*9SgQRPdu${Qe z!qeZ{ A2^!51q=I!zG_xbtz{QUj>`~3X)`T76<|NHy<_xJbo^z`%d^Y8EP?d|RA z>gwp|=;Pz#EC2ui0EPj)0ssjA0PhJLNU)&6g9sBUT*$DY!-o(fN}NcsqQ#3CGiuz( zv7^V2AVZ2ANwTELlPFWFT* }bL!m5v!~CWK!XY$O0=j^n@E!? zUCOkn)2C3M8l6hDs@1DlaoWUJFM!9HHTCiH_phwkvuM+*UCXwu+qZDz%AHHMuHCzM z^XlEpm#;s5`tou0im_h4ef`S%+b1}t @P5dnOP@}C zU_XD%8wPlOu#~7y=>9za8Be~v`Sa-0t6$H){gWy1<7=5uzy6o|`0 kN9BL6?eC~xP;)o=Y_Xj$ih*HWy z*Xh&SL9ktCkUO2kQR0m_=4jrBQ*>ybhWcT6VTD8z7~zpfCh1_2OvWMPg+SuxBYiyT zXyuje#nFj72Km!ZYYNq)+l!*uh~=4R?)c%9^F?{zlm9hYp^|jgsid3~!s(xzU$E)n zhiL|SW*njv6aZ+=!I;lK`V<6;ppah4V~ #x8D3#==|7W>Mv$LgT04nj2BEE3Qn z0mZaX)M9NdRAjrwwp)1N?YG{RA#NGNlsils=%&HOy6eK>?z`~1E62R_(woP<_~xtc zzWLVcZ@d7*!S1^0reW?d lCzDq6H~8+d%D*L0%B9jBNAGIP==ED;vL(F|*EAJaNPiH@xt-2_IbWzyZVS z@4Wm*eeWJpNBy)MO}{&E!3QUNw6_gE9Pz{zU;J~=JLjzS+8?_T1bJk%Ly%>|8MJEM z_oy=-&20Do?YGW5_nh&?LQ_k$w%mU0MbhS`%XHRoJT3Jelvi%~<(N}0_0y0G%x=0} zkE?jt*^W(Z+0JH+cG`cp&bHg*sY4JxjKa7oLGH*LgzLP&-m&1z5+3c)*A|`l*W)Vf zIJ;R(&Tr<>N1wUX|2Dlf=;n(4HR%vvOLnwoFT1+Oy_awE+hlM#5Th^NQx8AT4HU`~ zyPI!+$boM>xbV^%UcB+$IvV53wY+vU&3XBfo&pz$xqWfYdY?mA=)|?XZ9S}e*7}~s z!l$h95$k>+4A$F1L6Cm%gJlB2hY1Pts1Ul)S3BbwvVO+D{#EOLhzsDt26!%XJq}?0 z+7|==he*WAIj~+3B$x!Vr@e)7>w>1E82D1BI%LK0g;>nh6H+x0_~9*uD;x+YT9Cyu z=5L0<3)=Afhb;giFL?wkVDs#yuOjY25O~a^9`|U+BhD*p5nLj|n#eE GCB^91zqJ&N%BtZ&tw~C9#gVK!l|w@> z_t3Xm_Ofk#C{giMQFxsbmrG?TFWZT_j|P)tjx4O&sv2074tANU?HgAU8&DhnQj@XZ zLMUW`xKp%d4xE;)hH!^V+~N|~vfz{}Xa5MW&nmTo4l5lZ-KofUqE@Q0y%}r2cn_=+ zHoG}HEGKDO+ppp_TsD2ILY>yzJf!uNZ#Axb=WE=zesi<;Dra+*c+2N%G`i9y?K`b| zMeu4Du-x@7Yz=&~*#b?PpFCVaX^LLQnsUAGI;(pz_f5Cb*BT%Wv4}@ZU$@@3fy*_j zMfr (-DizQCSGWg7s9s1VOT4CQw+ sVMjEXkEdZi*r!`xR&|0>c~ zO{uT(XX#g5OpQngWwx%Fu!5nBx1pZwGp4Oa%$nNI*d}ptF{{UEKf`i<1emkakg(8t z>w*D(r?dDv?2iE(j5$_myDLqsjU79n5`Gx7$K`Bj3$b$Eu6A&V`(#BJ{NM;rIKo+d zT;%4_+u){Xq2J}~a@+b_=w_$7n-$f02kYJ46}HEJwaI$}S=m(o*tfX)ZPsY>bPwKs zMjkI)+$Jmh<~Ub)leg_~LB!mM5?3+B3uf`7W&Bzj7XrIS-YbwRSjSB7Rk2MTt$Z zx8-IzsQ(S~fa7uC!yR$X$4>TxYkRmo|GDOZE^eZidtpaM`Y-R@@u=_0=_0p#u1bFI zs>}3(Dj#>txgPVGFFEWYF1zB(es ?RSFeyRj=*{oor~_^%(n8i^mf?YGbU+mBtu4bLq9 EV$bmSB- z6_ I&e3=n?{Hh;!~ETm_D&UbY+#c%22Z`6l^uGdxs2VyN) zdoh@WT6loor*nU1gMl`FIaq##CUox7gLd_U8~1)uM}$zPd`EaJ^_Oga_k5@qeNVV` zQ7CgMxK=B8g#);Sf~bXEm~b^%4N``K5Lkv1h=v#ccW&!f3qTlV8+c3!mUngsg2*O< zcbI=9Sc3oNe^e-30Elq5H;AaXg@tH#3+Q+^$a_b0e&m)*6=;Ey*nStoh9l>O^7eso zXl%@ CV#$IZ%CMgoY;h(Sd9BuN0j4rhgXUQhlPSTjrzEc(>RE==Y?O0h~h_n zlQ(q0M~jeXi<3BGo!4%>XoTu0hwNB~r`L)4Cuhglb;&r2%xHzqNPAnzk0Lpek)VpS z2awhPhOVZFWY~>n2#)6F1>%T{n@5Ru$BU@{kdr#OlRVjzKKYYC8I(fVle- zqGxFUg_N77LwcEB%Tt1Ni7!-HeaZ-Xd$yIcmzH3uk7Ah)oe&D6&<>?w53ETF?Z6J} z;0fqJmivg7Ub&Wv(3UCbif{#&&oGtvQkR8BYvQ7pch#2y#%N%cT8-J9&iRvy`JB?} zoJIMIU1dW_xh>zql$N<$^b(a)xta9;c$FRbnfDl)UTJ`&X%3#i4y$Po0PvckFrV`| z3h=<1_5cr|pboUzj|G^PxT%|9=$mpGoOa1vc&SuNd7OReOB}?8(;1 #7=r*hdk42?faZXUm glyx{|K;Zdu<4Fj`6{9oS2wIp9 zqF@j5zz(u0q(JJUhKdg901Bi3U=Q)I4oX^*S$LaH%9eV@bG=EB`2v+zYD<= Deao_(5y>fjFcAPUF&qo07I z&APA*tDgsp4(dP(_P`G45RH?nmX!*n;U}d!Sf!p5NVI6K=gOt&`ZHk4EAg7LANsB; z+p-|~s>Bkja7r!rx;Ux-cdPD_s~gsJDY&OvDWmB5l`m)s>yV$H0HmML4h*}rOe+f0 zx(@PC3hH37+sdt$nh2OWep4#0AFD*=x>4u~EnixyE?c%2%CctLvTW+6_Bvt5W2=wT zqCAzSKI^N%T7c$Yt?Y2H>d>f6`?m}$55`KZ@BjdvK()5n2my+<8_SaV;;~mci`t{5 z(G|8{I<~!rvT0kkXuG-by0$WFTeC{5_*$p>DvvyCeLmZ(K>MpgYk-{4sO#{ffC{*_ zyR`8D3bZS`h--jWo12%~a2(r`;u^V $W;{ zvp=M>^N50e_^+)03$%AjfaWj?^e_O_s $WC_G#xoJVpAoKy ititSnp%!MVUKR&2pjtikKj!5$0;ARNMZu*A`TM b+j zRZPL$62V*Sz+Q~TG!VvR+^S?e%Vc`SUz{;(JhM=c#cmu1-x9}i%my8dt8%NyeC)@6 z9LR#q!suCmK>P^>E2Qzj$bTCTi7KS&01u_W377oB^Q+0-V8q#g#Mc1IOKhNA3&m`p z!N`Ehsl3XptU+9yF|aJlw(PFvC~0Dpa2e3#`iz%*(z!!N3g18N9)BT*r4T!c$4Y zehkQZHpoE>&6I!+&q}*MYOU71w4;#CLaGk*zz*LW#01E#o7~COki_U*piS(t?A*@p zjKSakqRLo2&%RX8ul&XM?9XXh%Rb$qI|<7)(7?Max&{5lUNFUHFwDfv&>-y4dn^cj zEL g%ZI~mk!3e<$Hp+gPJMok24+{Rm=)J#3hbo{|Bip*54%vU|p z&J3(CtPb|z$fH0CK$;F?E!sEAn(mO+)cT{O;11sW!zTR*l)A~BT+W_+&Vo?acl^Zc zoYz#m*EXHSt=z@RBG@{4*l23l$bF%SjmC@3#=m^f!mQB6Oxbvx$0MA~R-M@tt<^67 zObM1i3h?lwpun2b>bHz63W|)Mdke|a3JRgX4y54KL;BfmebPg`2q`VsES=jfZP)M8 z+rFL3SA5gMz0*8>+{- iwhWJ+bc_53dc*_&vn5&EIsL&H&Earaa*BY~ad^1b}Va3clbT z%8Le& | n22-3abUX)l><50J znNyw2*O1xGJbRE3=Uq7oblwiBjSglF a33H zRgT7?z5`jlU zn&-_5=s3*YE)EZePUl`3=i(gK+3@HsE$Mzh>2z%A7@X@Atm!xnEuH@94jk*SOzN#Z z@ATf}s$TE-PVXbI@BGg13LNX9J_`ch=~|8p1|Pv*e(Sj&=I)FP%kc0W3=PnL<`gg0 zhj0jeT*4^4!iyf~h)(CBAP>ii;`lkO9BsphD((0Q=nPxQ=uqD3knM>7-tmyI!YG{a z*KqN5P4Q?h@g4l|%h2!(uLkL^?gozw1aAuhKMMeV m@e|MU4t?<$-^YN=@g4v1m$0L(y|737qhig-K&tX*4Wvt} z;^`o`GEe6;Z}T_b$7-+RJnsiSKL 6t zN}l=qzV!e<@LqrLxsdQ;zwl(g40J5 a9wy|5}yyNqnwr~T)A z->}yn_>(aB-2liLfA}t)_>0f@LGR8(5BZXB@JX-qOyBfRFZ!VW-~CDM`QE?zqCWzp zAMmD6@L!+$VUO#%-tZ3Z`eu*zvXAo{Z~Jfm@i31LB#)oH4-n|kjj~tJ;6a4!1P(-4 zaMZ$v1=XQjXAd1EN|Q8dgw(NPw{F?8T_Z{I(4lB4qvc`g$E6==(B{lswhY {jkiVGTO Lg(HcEAywyk?+7;+!o)UKYNA|?AtKn#4lMo zetf9* n#QI}SPJz#G9k6upxUEb6SgF0Hjvlo2`;#Y3*V;?VO8u)ub5Z<(25f=03V9D^(} z{Pf$%8~{X12SLx!A%(z$;_-~MgA$CW9f3$;=fRC43~9oWDpaYZmxQFrCJuL652&Ds z(`!V#N(664Jl$GREV5k0F2+0q-Ks{q#=}uB9o6gqQKuh+jL)SaRXVbyBrCh@$<#c7 z(zA-FEU3ROH$?|DD+g)^OpYp(ur@POLenKRHDnW~4t@GC&Jc^ElPS7P6!h1t_H5C* zKeYq)SgHuE>qex^OO!|Ud?f5WA&G3vNc)h?hB8YxTFn!5KoM0yPYVJi9f(9FP#|{P zK@~Pt$7B^H3o(>QR^MucQ>a|$Op3%_??e_@Vf!Q&Mq>vCR#_ShZ8k?-`cgDnMyCZ5 zz9HqiR?=)K-8R2&HCpYH&j5^4%5u|D`Ls<3j0jy(;(1q0+9;&=%nQ@Za6?+%^bkZ5 z|E)7%<`n+5;OfjO7Od@z4wPYs^MY7nXxp>@*ipq6+c>33CHq)OjXtqwqD~Q7Fv_w) zV@G9lN8MCY&?XASo|)sVxy+kg%~|I)^Yz(Qpmi0RSEmGa8c(GCM40Krp9arHyAIte z;`FMP7AI;S)tbJpuc_3d%SJ94p-==$O`s>KJa=WcO-2Xqm(dC3ow_|*)n@u$g=yzm zbNYK GR Fpuq|D#gIz;l z=8o5NgnyFIpCj&9Kl;fpei(9}GuUVUho0%=e19R|Q2JFAynHVT-Ah98qEeOY#ZZPb zjA7yqmK~ _kLJh7NXs zs)3z}5@<}bMT5|TU8IO0BqmtF3pP=M(%_)`J{UqZjPNHV44!e2qCytBusKuVVITcS zJsU0$ht2zAAq(k4=FlaG;CtBQa8bmcoKG-=Q6Kx1m@${Ev5AMcUj?HG35{^V9-?vC zL9R%UwxPovaXXn81p>x`kfKZMDB~IRmqrY(v5g*NpYL`eM=;Rwgm#<-9=oT z05oJF@vz6~0@x5x+0&J;Q=35E5sFlKgn#S2pGoSMk`3Am8sX&GZ#pC#e&tG=Lc8X2 z_Gp!9X7qZ_1k2J0sZo$lubNP)=v^jS6mC{-Q57o;L&ur5_??q}>D LAPO0`XQ8!3T0^TCiyd5NpzxwD_&MY`qk%g z^hJXNg(=K>R =)AP=*g8;)STg JV~8qT0i!p_qqeV{J=$-;Cbld@sH!y=z_*XQIE3f}nwH@FNqvc?2MO(T#p|q$geJ zOJ{o1i(WnO3_ A(BUwwk4f`X~0TjlbyCWmm$H4cbF#3k&Q@*J=2Mi z+XGZ_CwWlCoQ`*}qY{-cZ)az{NqWO7CT@IVzU7$pUhAdm-(#WFhX9`HIbHITr~KrF z4vNZWUh|r7K<7Q*`KV9bdyB_W)=mFxN$#W>t6%;4E~z 4O=@<40u 6cf3``vGT&xc<0Ng(~hye|gSb6qFd_j;bgE{)mCvf8t~Et7=3 z(*#n`40J%b6DyZ2SqL{f2vA9fbqED_Ko_wIh30cUIvYUgqdq(vC&IIX?3*O+BcJ;N zKb~W}?s2@wV-EQ1JolTy&0{|ryg|x~LC|9k$D=ynGK%{npVU)5*6Tlsi=P1Gv~3u` z+cUsQSdFt;h_rD)g9s2ZGYEG$nTbdTcyNbv*udxWKw6_8*{eS5<0TSgJrg7b6!gCT z`dYm4dqKI-!3MxVJ8U`|yhA-iIy#g;AAGb%3c_9(!u{jF_W8Oa1i&P8A|@=r=9`-W zax5xr2yh!UgaA1Mng?~*z)G+sF~p$hGsD5-rR-~lHEcuEdqeSSLGpt^9o$1$EV?|5 z#Xamp9-P0$14JP7J|QH;Qgprd0Xu6`MB1ypCOp7)cn1-Sz)3WS0%|~1 zgMKiJ)A{zv_o5ziyj=ksw+KS?8Qkk#6$GIu$#VO zBtS>31av3`G&7xM1h1GO6_CR$b|?ohq%-KVMo`SgtjV(xEWzz#Lsa}eViHIHR+Pi2 zpu=?3Lv-xO8`MGh8v=J^vZA;@{2N65Q-fcu$3!H?EL%c-oS^1YvwsX1dI(4=%qMyn z1#_TAg|x =F*qN&WCmtv$qoueA;AW{M8fIQhHv=G0W`+^ z83)3w$#-~%p;CuZ&`D1jg|?ZSQTW0QB+PNppTP8nzx;;21VO#T28g`Nq+~t1l*{vh zOSkj|wp^i%M9Wwx%d-4Du#8RFoXy#+O4__l+{8`ub4{~EOSMeR(}c_axSUJ6q{q8N z#Jt1?`PodsyiEO}%$jtECP5u$WK2>!opc}wc96{GqfE=Z%)i`B`O(YI+(vF}2D)Sh z(iG0qOwBlCP2O|?^UF>8tk15rP5aEx{M^m>^i5m@PG1mC(o9dgyvsb>OTL`G%yiD@ zEX=(_hjy@sC{zbaWXyLEg|< C!@r)nx6hY8DPxKs3xnxh&bkEg1 z%lMQ}*wjxN&CSb81su&$9o Ljpoe$(1j?k$I1L9kb<^ Dxm>| q zg748(Otn-krBpAi%VvPoF)f7TTvX=#%R_C`LLF3ffCrTuDuL*PP?!<|>B&$?vvv3n zg&0)AEYxKM2NCVdMJ-WiAX8#py ({jyPWewM^B}`4A(0M3`b`XVi00nhe zU3K^bPzZ(0C t998lt=O$CR5|t5b5MtM7=^_I4gNTn z+O^%=eOt{`RF|FGbhTT$#M>;@UgDM4ofXwl9m`TR)gp~v1cqJ&F5JaE(#Ex3?2Q6m zt=ueiSnthT@ZH?2#ai+$-_R{gOuz(AP?=BY1a(juO$cF3h+p&FT5*M2-QC@DrCTwT zRN&=On>|f<<=ajT-~lFJ!3Ej{PFw{Z;-YQfBqd8*z18c@)yaL@)AUsf_Fm1^U=HqJ z&^6z2z=TZDgiSaHP6%T$9%D{8hfUCgOju$6`JG?-Ra0%aU;M4xU}XmW?cd?u)Z#VX z$NgcS4dMefV#F eX;38vgA=H52ITnrA=@V(-`)LIvY({b?PbJ%1v z)&w-p1W*oTFaBaMHseef2e-vt7>?mZjpG{DUpfw6JJ#X<)nlIJ ZQ7(rSK1gL47zGIV^o$?Oc-TSu4uH5W^l&j zu-*hrcwvR^>aJc?tVU-st!mbjYCa(8cC}QeE(WIl1*O&n0FLL!W$7awYQ_F(#%^rK ze(ajQJjkAG%C2n19_pk$YNYPer4H=Bw%qj8>(#4jtFGd#)@r$Cgul$>jc(H|-UQf= zZP}h}+K%lM=IC(1gfk9@xK8c=a;<9`zH7X02ED#vzjo@t7Hq<{<)c>K7r1Ppu59U^ z?w*!x>b`F5MgZsz>Y~={M2^zI_UxwyZDtVd(XQ&NuIqA5?Qh8BaQ=qe_GI}s>)NjG zGxp?DcI~nrhi^#jt^V!0?p@N(YpLdIU;XRA9_+$a=}<-N=ict@j_}I1ZV9h&$KG!4 zHsrDN?(dd@@D}f4fa=hm>hmt`h*fXY_UaIR;oJu0`F?R2kMS7iZ#Er=Ob7@625_tv z@Zl!z0w?JMNA3hyYU5>aCPi!vuK){Q@}H(~CVz6E-tNri@FwQ$&eqh=Chzhl@n%5p z^u}tJRc*r5S`{vHY(De <9p9TUqZaq(KA}?~yc5n >_aP zTF>=C$8~kL@Lkt%qh9n5_wXxkYGKDxg5GjIpmY;Ib}-jVYsZGJwpeG6Q)sVt_#SJO zy>?}NV`bO&GX?Jd;1+IH|Mnk`<-X2R1m|;d_tXGxaCC?8c7JkqANdGR@+p`1Mu$>h z$MSps)kmgFVkhl7mw17Pc-3YEz0?MA{_3seYTV^&i~Y-Fhj_h|_&WD?AK&Xd*LWf4 z_~kD5SqJnaH+d#6d8^0nckkzTe_TfY^?JAW5ZCgVH}(t$b5xgefG2fpm+OUI_N_it z5f%ElPIjG__=yiwrN?uO5Ar>S`UN-ga}Rkz&w2~Lybefw#b12JZ+yppe8`V{$(MW! zsC>)6d|1eQSXctiR|3!fd?nxl(I5TM-v!e*1JqCbzkUPOZ~Z%XeL9GJ+1KkqsD09& z21CewYS?}MYA6J5;07}N{ohA~-}i6fKYngVZQ(cm<>%_*M+D->YTl=Y+&6^Vul+&L zYuT6m*S~|-Z~ebc{nR)8T_An)2mR0I{3XzQSh#%mw}8oyfBBz(`fq&1uYdf{fBjed z_Xh|I0$~w^wdCLuB?=cR;o|V&!(9^1P^?(&;+u>b?{K8!@gtv*gGNp(In5ACL)2Cl zYT1$-Ohhta(yVEdCZn7+bMm}-Q)VGVEmy8gX~^WXk%L0=`S_9J9gP|<7OPlM;x33< z87@S)q#!JTV#jtUYxXSKv})I~ZR^%;r+aem@#}|g?p%ER_~8Zh_AlVTg4d2MYym7n zt_rpPYQ<`mB2}qxpgIL9QmIL!D-msW8B``BpQ2&v99=VXP0vG#8a1hOX~@bRp-QIu z*bG+25EUyy2-Yy!!NP|VFB^Am-Me`2>E%1uZ}I5T-2xYjII&{g4jtc)ES0isrmvA| zCaU@~=$WS1(@YQgJW*<`T`OfOn+|QMwr}Uw&fDv6)mbndfdv|eS8~fSw^v^VIyjwx zhZTe!SK4hi(PQ3y*HL)lk(60^=BeghdhE4FB2cQuC*OP})n}h<`RR8Ne-c^9#eW9{ zSlxp?4tJbf2`<-MgFYG=ScKOd#M_PBS*77d99G5@ALD_D(u&lahaQSudbwqmEhhE< zVr(&TB-tDLS=FC^iUC+qgh$E=7lL*vNaS;K>d97;6E4Z5c3ADw9Y&IYH>HOkUWw(3 zTXN|oiH?RT=9nW xZJFCp{%`eydw#{wRo3q+IXQ~m DMmDfyfJviYL zPkkrC_kdD3 =kA>I%5vWfw;`Ut%(>1f->WQ|dGo8= $Jp4Fw-oD*;XOzC)MDuO`z3YL;9zFmg z*1fzWZgBxTpu@&TKCqc@Y?iy6`qo#O_K~i0@$*~-OXs}$ ~*kP z3j$N9umQ>?Tewr93Kf_!giMZd4iw(-FekwYsx5;SjGqrRXu9Wh(1StCp9mjP!~XdX zg)l@Q3m0@d13nRfGW;3?!#1l2njw88E1AhW(ms?0v5Y|+BhQFfM5*A9UrAIV2}{^S zwNw#`b)p&pr&vcTUh!L5G};z%_!#1??_4KCqZ!MH#x!yedeQqJ65r^M)-e%dZrNi1 zqv%3VJfM@F{3IwtDauijvXrJgB`IN`%2l$G2d;Fb5n^eCE6}q4mb9#4$AqCvVW6Rx zzU<{Of_aSeDWjOmzymUqsZ2cRp_$HnCN!hz%sxo7n$}z G{P%g$;x%Y0F~~1Cp_b6N>AJoaZ{8hKJ%&1RI;;`uRP~0%X!OjhVz@g>?Sd_ ziA`);v!M=ss2&!&Ol@uxXx%huFT*)bD+rX908M8 *5~NPeDqcrA zRaD{8s`tz*VDstJbn@VwU)`t`Zi-Q|CiJXqO{-cTJ5i!q^rASGD=xo!PQu ccBz5X8PPf*4*lLa=eWtXFH2r *~{({u^go zli-VItAR%1e3R(w(E=kDpIWC6|=a-E`BkLV=Utt(>TSU zBms_dyiO+Ym m`W9L2dxz97c@taAo z;~uM77HIzJ70NtjUO-qFVE(d{r_AL^Q@YDue)N+Yz34I{8qI}fvzr4QXFrp=)O*IW z_w;P)RkNDLfY$M#d0c2T6Pc!E?!uxQjcF)Hx|@~0a<7HC>r9Jz%(p(Xr_~JVP{SFN zt$sGNSB&b>YAf2-hW4v*%uY7X`p`vI^q6)3J#1bFyWE!^x05w(Y)&J4$e->pJ4Nm6 zYty^b)AlQh>#c7;XIs#*mNk%Rjp$qBI^5z$H>AxSOoW5E(ZbmDvD+ WG3@}ie~aE7zI *hb~H>b6wgdPPNCoE_R^@ z`rk(nxXG1{ +by4nlQ z_O`qI?NDaC)Kflpy3_pXSXZUi`5yTHo&SA`3HUtdLofQ#lfLw(KRxPGulmzpp!Kf5 zz|d 96JU v6|&zH z!XOhAp%ES-65d}Eu3`SAVHcdC3mTvlPGJ~cz!%=(6>cFY;2|ISU>Hte4Ul0OnxPT8 z;jOKqA(j~&h9DLQA|QrgA4Z}L>Y*f7A_mqWAPV9j${!piq9>*y9LAv>lHnslq9vZ< z15zR?u3`hOUk|(@EcSr=(E=^jA}welBZ^=s&fh13;x3BfD8isE)}s5xA}p>SD<)(4 zsiHDAWB9qEG0NgB4&yE2VlMt7HfAFM=3*miA~n)N0!CvGAfq#uWB2JH1f-)nt|L3P zqdUGMJjSCu&Lch6V>^ofpE!zR0)Ar^^rJueV-^IYKn`R;a-%^WBta%*7W^Yaeq%W1 zqcMt~Jx(M=R-{E QCM|5H6Evn{LZ)S==0#GbYPRM?j-O-tWi*l|XNu-vf+lF%=4g_p zY4+v#xh8N@Bx?pIal#{P#wJA4W^L-GXND$o(xP%gWNbbrY8Iz;=H%B=o8m+OcXp?D zekXW_r+AJhd6uVno+o;yXLrJ5478_vy61HIz!A(Reb(oEDkpyC=VIC?e;z@6z9)df zqk0Y~fflHN9%y;$A%ZR_gEpvxdS^TW=zGSeeD)`Q>L-ShrG@e*g-WP<#^ZyAsECf} zcq%A~o+yfnXoPxbds66zzG#Lz!HaHahq7o4fM|;5sE!t>iSDS6{-}AbD2q~PSI(%B z9_fq{sSnuy=#2^}kUlAtc4v=9DV6@HJThqvC~1)*DVO%AmM$ri%43y|>5fh*nV#v1 zUMZGl>6dP4m)d8WcIBEjsgt6qor phDtZp9vpy@d z>Z-3sE45auwRUQHE~~a~>$axnwSFtOLhH1KE4h~Iu9g70rmMQHE4#L aH&9wyx{GF6_px?9MLj)~@Z|uIqX(?(Qz{_O9>#F7O7g z@D4BWvaTL30P-fU@-8p)Hm~zOFZ4#Q^iD7JR X@rEz? zmaqAqFZ!mh`mQhgwr};0ulvTY{LU}^*025Eulm9-{_ZdT_OJi`F936|{su4s7q9^z zFan?N04Fd5H?RXgFaqnL1WYgmSFi Lu@W!w z2^TRFKQR qT@e=!(`@eFsd7?-gbpD`L=upUkT8@I6= zzcC!gu^i7a9oMlP-!UHNu^#U+ANR2z|1ls3vLFvKA@A`T7cwFzvLY`sBR8@mKQbgo zavmSDBv-N}Uos|VvL N4v z=d(WVGe7sUKmRj8r?Wi=G(i`%K_4_iC$vKQ^FS}OLq9Y`N3=vwG(0ynMPD>VXS7Cd z^gdfOM}IU(hqOp@v`3G$NuM-Ir!+s8v`V+MOTRQsgL5`}08Q7lP2V(5=d@1mG*9=m zPyaMf2enWSHBlF}Q6Du@C$&;9HB%4uOgA-DN3~Q>HC0!&RbMq$XLV3NwN`hvSAR8F zhqYLbwN-O9S)Vmpr?pzIHCrRKS+_M@$F*F~HC<2jTh}#S=e1t%HC^8|U;i~=2ex2S zwO YNxhpuQqG9wriJmHovxP&o*t>wr$@wZs)dc?>1@2HgEqna0j<=4>xfacW(Q( zaVNKOFE?{Hw{x#HazD3pPd9Z}w{_cgbYHi2Z#Q>$_jPBtcZaulk2iVWHh7medZ)K~ zulHtWvkAO6e8;zZ&o_P7w|(C?e&@G-?>B$ *ArxPc$If4et=FF1oY zxPw19gh#l9PdJ71w}MwVhG)2jZ#ai{xQ9phg@3q+k2r~!xQU LY5xR3uhkO#St4>^$+xsf0LIg%&2k}tWC%Q%xiIh04a zlurTV3>)(~l~=iyUpbZoxszu(mv_0Be>s?kxtLdZHlu) p7->x~Pvjsh7H`7ka3lx~i`_tGBwTr@E`hx~$JSts6S5 z*E+7}x~}iKn%_FF|2nV-`>RW~3k?7V96PcnyRt7kvp2i5KRdKXyR=U`wO6~fUpuyE zyS8sTw|Bd@f4jA3Gq{gCxtF`SpF6szySlGCyH~row>!MY|GT`;JH6Mty{r4X-@Cr= zJHPk4zyCY8=R3d;Ji!;d!5{p&3p~OvJi|ADNJkR&M&;LBo z2ffe_J<%6^%V%>4B)!rvJ<~V6(?31bN4?ZfJ=Ise)n7f MJ=b@=*MB|OhdtIK zz1Ww%*`Gbyr@h**J=?dv+gm-^zrEbgJ>A#6-QPXluYKI-J>U1e-~T<}2Y%P@z2FzV z;U7NYCqCK}zT!8&<3B#+CqCmxKIK=w ZiWyuRiOyzU#j}?8m_JMzVH7&@T0!%2S4!_zwsYG@+ZIY z_x|uNzw _=msvZ@>4Czxkg(`ltWym%sYA zzx%&G{C__C$3Okozx|Uxu_w*|+&}*VL<)fe2^KVX5Me@v3mG _M~|+ +?{5z z5><;-ZeG26`Sy)U7p>i`c>NYOd>HXzz(YOW#h5g4WXY2$m-Se;@!-dmId}H_8Rp`& zn(sjdeHwLY#G(ZseJlfZY}vDE*S38dcW&LgdH44H8+dTx!-*F+ejNF4)|p+GPW~Kv zbm`NnSGRs0yYJ;6Ne{^W9ejB4 2XP9Pzym9}@t@ z6jfZ2#TH$B5ylv0oRP*FZM+f39Ch50|HmGE{1M0-O)Tw(B8@x}$t0CrlF25Wd=kni zrJRz=Dy_T{%Ph6rlFKfs9C83J#T=8&GR-^_%{0|qb4oDTd=t(%<(!kwI@NU3&OG(p zlg~c=+!D_}1s#;oLJd7L&_fknl+i{NP1MmyC7qN~G$E}N(@ZtpbjeFO{S?$tAH6Iv z=0*|~)l^mG6U-l6eHGSNWu2AQT5Y`**IaeomDgT<{T0|?g&mgIVvX(f6H*!D&c0)v zeHPkirJa`AYOTGN*JOh#mD!Q7{TAGC#T}R2a;??&DsI#3&fIq0eHY$% h Z+~28tbgJ-kR&Kz5W{Pu*Dvm?6S>18||-~#&@K&-F_SHxaFRk z?z-*1+v~M2ej4w-{r(&9zy%+?ZoL5{9Pz{zU!3vA&o;a}$0eVf^2#kQTv?FYzMS*U zJ^$Qu$n682^wLc~9dFS$N1gT7U4K1m)scdo_S$VPU3R{2-<|i~6VJUg--REZc)Ecf zp!npKUmon^XLug^=%t^Y|N82!zaIPSwcnom?!EsW{P4vepZxO8Ki_-j(O;ka_T7IU z{`lpepMLJuuOI*X_1~ZW{{4^te*Y8T00~$?10HaE0Yu;e8Q4GvKG1y%gx~}zSV0Ru zP=XiK;08I^!S`j*gCP{*2uV0S5S9>yDO4c_Glw#ytPqAVlwko+I71uW5QpZgVGeoN zLmyV}csLN^5Q$hsBOVcnNmSwznb<@pJ`svhl;RYrSVb#d5sO*WVib>;1229NjA0bx z7|B>hGoBHRX;kAH+1N%mz7dXbl;a%fSVudyv5R-q;~x3gM?d}%kbxBBAlH~jLLL&4 ziB#kw8QDlc8q$%G|CHn;DOpKN$`O*6)Z`{P*-20KF_WJZ 7H;l&MtZDp~1A zRJIb9v6SU3#rR5E-V&F&JY@@E_849M5}3iHq%DP6Ok*Cij>JUfGMU*-GAh%V(Uhh! zp*c-!UXzy9#O5}+X-aK&6P)4H ks z)aO3=*-v_s)1Ltq=s*ctP=g*6p$S!}JOR2;hdvad5tZmfDSFU`RurQd)#ye!+R=Po z)T1F4=}1XhQi6iiq$ySDN?BS`l(rP6F_q~|3;I%;-V~=f)oD6yx>KM26sRucrPE3i zRHGghsT=KS|5BOSRHyQDsZW*aRH?eos8$uLS#9c7wc1s$?v$%v73)}A8dkEN6|EsP zYg*ab)`_mwt#OsBMTg2Zq|6nsd4(uk_1agzGPJIHE9zefTiA~V7C?qo>|*1|SH?aT zua1T6WZNoP%3hYNmc{I5yQ*2vewM191?_0JirDUu7PV{@ZE9KT)77>Xwl{rkY-tPA z+13`fDZOoOc`MT0_7=EGHLZPuTU^%;cO=GDE>3^DT<0pZxzCkuO_6)o>0Vc;+p<_< zvD;m>R`)U96)#7lJ6`hwR=b+B?s?g}SMUzDz43+adj}w2`y#Zw_qA_+^&4OQ_Lsf? z1+aMq|2$yv7TCbuMR0 DX**&P z!`8$nW^Ia9j9M1Ac(gBu@n>Z` uM=7uwJnMs%V%yl6&$*wK#`aik?(VoFyU z#h1qPiZ#7y7kApzF$Q(0X*_CD-`Lcr)^Vy;-D6g_8pyAP^^j#fYa`d%)=9>7u9>`R z|6V`Y*T0rBpnC~lVS~2M$2MuQl}%D+H`}+thPG;@J?$`8+uCF%Hc{)F?Nx8vu7dWq ztHCYUZjT$X&qg;!)xB s`_}e8Exz-euI=ud-@4W}aRV-Jf2&vA z1@G&;6;9WIC%54X$9BY-%5aJg)Z!NpXvQ_(PmXtdpC1SLVIRKUkyE?m2|xL@Q*P{) zpH<{9|L4SIp3|7uys l8El@0`^EB)z5k2=w( z9`vgF{OUW;I?lIV^RCPM>n{)c%Ex~4vXA`iAWwV8*RJulU;OP9k9)-D-tf9B|NQO+ z&wIf4&hNhC`|tD)e7pzm?!vG8@aRtbxEJs3#%KHS){gwNClBq)H~aF+&it`AFYM0$ z`t!UFeXd7u>(bBq^sr8St5>h;)}Q+Iq>g>4XYc9SZ~FF_&V8kKFX`St`uB_uexiqO z=;9Ch_ ;OG10XJ*`F{}YA z>;WY#0v~Jw8LR>m>;ere0|#sa0jvY7Oy` w-w2KfvIYp|nG(93QR2Zd_}d(Z}d5D5S5-~!F%hR~>V(7$*P3Ezzgn^4}K5DM$9 z2#bvdr|_qgu)VY}z@!ih7m5qd%nQFTp~8^B#*hpht_ry=4YLpp+wi2`5Do>(49BYu z4@?f{(4X!Q&hk(Xp~?^cP@niv!2(eb@re+zD-j#a5D(Fw7Lm||&=F$_5+SS+Cvl!C z@wqf{!Y&aLb50UJaiutMxk&NQLQxcT%M(+volMcVTv5?bkriK17H3fg*D&0U&=$X| i7I!fRSJ4;G2^NEE7#pn@gHfZ3vA2{F(uge}0029P@-hhk literal 0 Hc$@ !vN)X0%lR0RQM9D!25FM*cCnwTO KDwm8T0suSBOiFqH literal 0 Hc$@ -kG0_K^8f$M)j@V!r z134K0V5m}oAO-vcb713$9u9?okdR0agn>eeQvf)giNzxEbO36ch7&5KJ^_smLm?;= zjb(v?Ard?Vw!`xfpW6g2IE+zd+x~!of`f#GhKGoWii?XtNRLQL9Zgl0RaYHamX=^* SWNDyfYmB6&rl+VM5db?0@>*8_ literal 0 Hc$@ *CP;hW?udlDg%G{5Sk5XTki RKvA4X_Z+fWU;Naus?iea`00030|NjQN zTmt|A03&o#PE-H?|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC00A?wJmH+?< z7fD1xRCwC#TiKH9C=A6L-tFv*yL%@1|37j-NJ7}%_tx}u)zo#uOYjoMM;wk8h?<|a zsTcGY^cVCO^e2R_t1e57Z&@=I&jMYKrA1t}3R;udgSso80D9Uuq^UerI9rs}#-Z!h zn1*quX-?-=V(N6u(3J(Q0kq0$n(JNf43Jy!ZN63Ld1gvCr;COpj#}&3p0_4Rw+X#S zj5|m(^GT~6_&zb!7NJ*(8&f;9U$oL*)um~+2fZ>$0pt30v$y73!mfzz4tZyO*)l70 ztx?;8t^s-TYvi>`Vch#oK`%}ETja>}yiW9npex7X-?5!NLFoSZ(6uS8!u=Y#aBIqp zn}MF0V$Pkm*I5QpTw*^<033QEnyC{A>xM^YC%ACi-{AAKIREXJiyTZ}{5bS9a#*ST zmBpySFAH(&y~^(e^c`HdKko3kTb%!HTj^~6!_Zs9x>YF!qu!}8oXMohS=(5e_+g*V z9{ur#myVAk_HIXeet-VYuU7!&h>Z|?{2+8K23<%Unqyp^m~9eM@yvg7(D!(7v3;_F zHOl=b-cMsNWldiGFm`1pR52G7sW-P#{B$(znSGI@#Dnv}!hI8VxO4ydGcQ;YbT3EQ zPeQM2>(X0NurRU2Pa7$8S?$ EAi(3MU*K#=*$Db!EW zswFv0SR8B?ZkpcTjh@MvF6}# xr4_ =Vcg?oPrkpu@Z6WCK>do-g8kd7^L@q(o(6 zbdHb!x}&bMShr$4aypM0KZ2R65ZxV+<&NXB4}Lz3UIdPygG%m$4T%Bo?uKCjy$I8g zpbI77`>cw2)eaC^L!?3D__^+mB) cG%R;~mFce`3D7c=jN|8W`gt zg1*NuETDseOc5kk6t;6MkD)8Z$M@Mc(?DVHh1y?Z)#~5@O$43z-K{c~NCv7T&dwyBX(3`ZfdQNxc=*k#8 zLQ5LGfKL3b*P*L`Uaz5h#(R$Yfj)SBe*_H^fauCF40maq2>K8~Z_+d@hY)moYD~iP zRm;#T_;u6ZcT*}o0__F#YQgxDlbZ0*!mJ4I0loXtgLL(TTn$5YWp-3^UH%g7G=pC6YN$?1Sg{8NxA~;g8P bC4b;UgmIIoP#j6s z `fYlXl48hVVK@5?B! Onn*nSNiB)nP&ymV77q&{=dGf@OkoW96p%`-mN z;lV8;PH^-^`a(p$3*UVbbhz?tK3`e!QSL4hyM%rTvnC5c*%CS=DD;XdM5Hb}hnvRW z(4i2=bD?x$3?;47wU5Mi{?W?IUT;|CqVnBIlxI%Nck+99bUrei3n~3clrHZ9`dOqk zwe&U8LGyc644vy3rH77}@)~q3Wu>=+YWa+%iYQsr#CMjy+;gI#dC|ocgL5cRxc;;V zV(!)~x4RhO6MKA-qO$|d-H2}Z#J zRUEpO#CPUQ3y2d^!(=fIlPJVZQ3d)UiV!
'; + if( $_GET['saved'] && $_GET['saved'] == 'success' ) $info.='aP)N=bdVtZwKfl$6}qgVw=er9m|qgl&p=er=+v5+}Azd6IqsI zmfu7_F2r8m0O{`SGB2w%dlZ0WJ7PWm)72jpul?3vtqRXB@^ZT*YAofX&MCL 9Zo)Bi~ghMhC`=pzZw4BHaJ6`T|Q3ZIrQ;CNCsctQsWf;w FZkzY ^C`L6);6aN7OM)+C(_Ji5WbN<=uY1#|=3;GNC3;M>}KLQK@JuEX4OGyzW P00000NkvXXu0mjfkXWNG literal 0 Hc$@ + Order Deny,Allow + Deny from all + \ No newline at end of file diff --git a/include/admin.inc.php b/include/admin.inc.php new file mode 100644 index 0000000..75b666a --- /dev/null +++ b/include/admin.inc.php @@ -0,0 +1,148 @@ +connect( DB_SERVER, DB_WRITE_USER, DB_WRITE_PASS, DB_NAME ); + +/* TODO: Move these definitions to LocalSettings.php */ +if ( !defined('RANTIMG') ) + define('RANTIMG', '../rantimgs/'); + +define('USING_TIDY', false); + + + +/* These function are all for core authentication. */ + +// Call mysql to hash a password +function mt_hash_password($password) { + global $mtdb; + return $mtdb->getOne('SELECT SHA1("' . mysql_real_escape_string($password) . '")') ; +} + +// Remove invalid characters from username. Permit only alpha, underscore, period, at, hypen +function sanitize_username( $username ) { + return preg_replace('|[^a-z_.@-]|i', '', $username); +} + +// Attempt to login with a username and password. If from cookies, set already_hashed = true. +function mt_login($username, $password, $already_hashed = false) { + global $error,$mtdb; + + if ( '' == $username ) + return false; + + if ( '' == $password ) { + $error = ('ERROR: The password field is empty.'); + return false; + } + + $username = sanitize_username( $username ); + + $login = $mtdb->getRow( 'SELECT id,name,email,nameplate,default_image,default_link,password FROM contributor WHERE name = "' . mysql_real_escape_string($username) . '"'); + if (!$login) { + $error = ('ERROR: Invalid username or password.'); + adminlog("Failed login attempt from ".$_SERVER['REMOTE_ADDR']." for $username.", MTS_LOGIN, MTA_CHANGE); + //logthis ('AUTH: Failed login attempt from ' . $_SERVER["REMOTE_ADDR"], var_export( $_SERVER, true ) ); + return false; + } else { + // If the password is already_md5, it has been double hashed. + // Otherwise, it is plain text. + if ( $already_hashed && $username == $login->name && $login->password == $password) { + global $currentuser; + $currentuser=$login; + return true; + } + + if (!$already_hashed) { + $passhash = mt_hash_password($password); + if( $username == $login->name && $passhash == $login->password ) { + global $currentuser; + $currentuser=$login; + return true; + } + } + $error = ('ERROR: Invalid username or password.'); + adminlog("Failed login attempt from ".$_SERVER['REMOTE_ADDR']." for $username.", MTS_LOGIN, MTA_CHANGE); + //logthis ('AUTH: Failed login attempt from ' . $_SERVER["REMOTE_ADDR"], var_export( $_SERVER, true ) ); + return false; + } +} + +// Attempt to login using cookies with failback to HTTP Basic Auth. If that fails, return a 401 to the browser. +function auth_basic() { + if ( !empty($_COOKIE[USER_COOKIE]) && mt_login($_COOKIE[USER_COOKIE], $_COOKIE[PASS_COOKIE], true) ) + return; + + // Either there is no cookie or the cookie is not valid + if (!isset($_SERVER['PHP_AUTH_USER']) || !mt_login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) ) { + header('WWW-Authenticate: Basic realm="My Realm"'); + header('HTTP/1.0 401 Unauthorized'); + die('You do not have permission to view this page.'); + } +} + +// Attempt to login using cookies. If that fails, redirect to login.php to get credentials. +function auth_redirect($showloginui=true) { + // Checks if a user is logged in, if not redirects them to the login page + if ( (!empty($_COOKIE[USER_COOKIE]) && + !mt_login($_COOKIE[USER_COOKIE], $_COOKIE[PASS_COOKIE], true)) || + (empty($_COOKIE[USER_COOKIE])) ) { + nocache_headers(); + + if($showloginui) _redirect( ADMIN_PATH . '/login.php?redirect_to=' . urlencode($_SERVER['REQUEST_URI'])); + die('You do not have permission to view this page.'); + } +} + +// Safe redirect, defaults to Temporary +function _redirect($location, $status = 302) { + $location = preg_replace('|[^a-z0-9-~+_.?#=&;,/:%]|i', '', $location); + $strip = array('%0d', '%0a'); + $location = str_replace($strip, '', $location); + + if ( substr(php_sapi_name(), 0, 3) != 'cgi' ) + header('Status: '.$status); // This causes problems on IIS and some FastCGI setups + + header("Location: $location"); + die(); +} + +// When doing redirect to login form, ensure headers are never cached. +function nocache_headers() { + @ header('Expires: Wed, 11 Jan 1984 05:00:00 GMT'); + @ header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + @ header('Cache-Control: no-cache, must-revalidate, max-age=0'); + @ header('Pragma: no-cache'); +} + + +?> diff --git a/include/cookies.php b/include/cookies.php new file mode 100644 index 0000000..096cc37 --- /dev/null +++ b/include/cookies.php @@ -0,0 +1,52 @@ + $_COOKIE[USER_COOKIE], 'password' => $_COOKIE[PASS_COOKIE]); +} + +// Store username and password in a cookie +function mt_setcookie($username, $password, $already_md5 = false, $siteurl = '', $remember = false) { + global $mtdb; + if ( !$already_md5 ) + $password = mt_hash_password($password); + + if ( empty($siteurl) ) + $cookiepath = COOKIEPATH; + else + $cookiepath = preg_replace('|https?://[^/]+|i', '', $siteurl . '/' ); + + if ( $remember ) + $expire = time() + 31536000; + else + $expire = 0; + + setcookie(USER_COOKIE, $username, $expire, $cookiepath ); + setcookie(PASS_COOKIE, $password, $expire, $cookiepath ); +} + +// Force the cookies to expire +function mt_clearcookie() { + setcookie(USER_COOKIE, ' ', time() - 36000, COOKIEPATH ); + setcookie(PASS_COOKIE, ' ', time() - 36000, COOKIEPATH ); +} + +?> \ No newline at end of file diff --git a/include/error.php b/include/error.php new file mode 100644 index 0000000..2dc840e --- /dev/null +++ b/include/error.php @@ -0,0 +1,77 @@ +id) ? $currentuser->id : "NULL"), $section, mysql_real_escape_string($action), $level, mysql_real_escape_string($msg)); + $mtdb->query( $sql ) or die($sql."
".mysql_error()."
\n".var_export(debug_backtrace())); + + // Log all important sorts of messages in the Apache log + if( $level & (E_USER_WARNING | E_USER_ERROR) ) { + error_log($msg, 0); + } + + // Email critical messages and those for which email is requested + if($email || E_USER_ERROR == $level || E_ERROR == $level) { + // Pretty printing + switch($level) { + case E_USER_NOTICE: + case E_NOTICE: + $importance = 'Notice'; + break; + case E_USER_WARNING: + case E_WARNING: + $importance = 'Warning'; + break; + case E_USER_ERROR: + case E_ERROR: + $importance = 'Error'; + break; + default: + $importance = "Other - $level"; + break; + } + + switch($section) { + case MTS_LOGIN: $area = 'User login'; break; + case MTS_USER: $area = 'Modify user'; break; + case MTS_PAGE: $area = 'Modify page'; break; + case MTS_RANT: $area = 'Modify rant'; break; + case MTS_SCRATCH: $area = 'Modify scratchpd'; break; + case MTS_STRIP: $area = 'Modify strips'; break; + case MTS_TYPE: $area = 'Modify strip types'; break; + case MTS_TYPE_META: $area = 'Modify strip metatypes'; break; + case MTS_TWITTER: $area = 'Modify Twitter'; break; + default: + $area = 'Undefined Area'; break; + } + + error_log("Megatokyo Administrative Notice\r\nPriority level: $importance\r\nReported by: ".$currentuser->name."\r\nSection: $area\r\n$msg", 1, SITE_CONTACT); + } +} + +?> diff --git a/include/extra.php b/include/extra.php new file mode 100644 index 0000000..0eeddef --- /dev/null +++ b/include/extra.php @@ -0,0 +1,82 @@ +fullpath); +} + +function extra_sort_file_objects($a, $b) { + $an = strtolower($a->name); + $bn = strtolower($b->name); + if( $an == $bn ) return 0; + return ( $an < $bn ) ? -1 : 1; +} + +function extra_handle_upload() { + global $info,$error,$dir; + + if( !$_FILES['extra_file'] ) return false; + + if( '' == $_FILES['extra_file']['name'] ) return false; + if( UPLOAD_ERR_NO_FILE == $_FILES['extra_file']['error'] ) return false; + if( 0 == $_FILES['extra_file']['size'] ) return false; + + $dest = $_FILES['extra_file']['name']; + + if(isset( $_POST['name'] )) $dest = $_POST['name']; + $dest = extra_sanitize_filename($dest); + + if( !is_uploaded_file( $_FILES['extra_file']['tmp_name'] )) return false; + if( move_uploaded_file($_FILES['extra_file']['tmp_name'], $dir.'/'.$dest) ) return $true; + + return false; +} + +function extra_file_from_inode($inode) { + global $dir; + $files = extra_get_directory_list($dir); + foreach($files as $f) { + if( $f->inode == $inode ) return $f; + } + return false; +} + +function extra_sanitize_filename( $raw_filename ) { + $filename = str_replace('/', ''); + if( $filename !== $raw_filename) return false; +} + +function extra_get_directory_list($dir) { + if( !is_dir( $dir ) ) return false; + if( ! $handle = opendir( $dir ) ) return false; + + $files = array(); + + class ExtraFile { + var $name; + var $rwx; + var $mtime; + var $inode; + + function ExtraFile($path, $file) { + $this->name = $file; + $this->fullpath = $path . $file; + $this->rwx = is_readable($this->fullpath) ? 'r' : '-'; + $this->rwx.= is_writable($this->fullpath) ? 'w' : '-'; + $this->rwx.= is_executable($this->fullpath) ? 'x' : '-'; + $this->mtime = filemtime($this->fullpath); + $this->inode = fileinode($this->fullpath); + } + + } + + while (false !== ($file = readdir($handle))) { + if( is_file( $dir.'/'.$file )) $files[] = new ExtraFile($dir.'/', $file); + } + + return $files; +} + +?> \ No newline at end of file diff --git a/include/functions.php b/include/functions.php new file mode 100644 index 0000000..2987b56 --- /dev/null +++ b/include/functions.php @@ -0,0 +1,91 @@ +$v) { + $tmp_key = urlencode(is_int($k) ? $numeric_prefix.$k : $k); + if ($key) $tmp_key = $key.'['.$tmp_key.']'; + $res[] = ( ( is_array($v) || is_object($v) ) ? http_build_query($v, null, $tmp_key) : $tmp_key."=".urlencode($v) ); + } + $separator = ini_get('arg_separator.output'); + return implode($separator, $res); + } +} + +function wp_get_referer() { + if ( !empty( $_SERVER['HTTP_REFERER'] ) ) return $_SERVER['HTTP_REFERER']; + return false; +} + +function clean_url( $url, $protocols = null ) { + if ('' == $url) return $url; + $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%]|i', '', $url); + $strip = array('%0d', '%0a'); + $url = str_replace($strip, '', $url); + if ( strpos($url, '://') === false && substr( $url, 0, 1 ) != '/' && !preg_match('/^[a-z0-9-]+?\.php/i', $url) ) + $url = 'http://' . $url; + + $url = preg_replace('/&([^#])(?![a-z]{2,8};)/', '&$1', $url); + return $url; +} + +// Like htmlspecialchars except don't double-encode HTML entities +function mt_specialchars( $text, $quotes = false ) { + + $text = str_replace('&&', '&&', $text); + $text = str_replace('&&', '&&', $text); + $text = preg_replace('/&(?:$|([^#])(?![a-z1-4]{1,8};))/', '&$1', $text); + $text = str_replace('<', '<', $text); + $text = str_replace('>', '>', $text); +/* if ( 'double' === $quotes ) { + $text = str_replace('"', '"', $text); + } elseif ( 'single' === $quotes ) { + $text = str_replace("'", ''', $text); + } else + */ + if ( $quotes ) { + $text = str_replace('"', '"', $text); + $text = str_replace("'", ''', $text); + } + return $text; +} + +function add_query_arg($param,$value,$url) { + return $url . ( (strpos( $url, '?' )===false) ? '?' : '&' ) . $param . '=' . urlencode($value); +} + +function Excerpt( $excerpt, $maxlen = 455) { + $excerpt = strip_tags( $excerpt ); + if (strlen($excerpt) > $maxlen) { + $excerpt = substr($excerpt,0,$maxlen-3) . '...'; + } + return $excerpt; +} + +/* Type names may consist of digits, letters, -, ', :, and whitespace */ +function sanitize_type_name( $name ) { + return preg_replace( '/[^\d\w\':\s-]+/', '', $name ); +} + +function check_type_name( $name ) { + global $error; + + if( $name == '' ) + $error.='A type must be supplied with a name, but none was given. Valid characters include letters, numbers, apostrophes, colons, and whitespace.'; + elseif ( $name !== sanitize_type_name($name) ) + $error.='Supplied name contains invalid characters. Valid characters include letters, numbers, apostrophes, colons, and whitespace.'; + else + return true; + return false; +} + +function _objectInArrayWithIdExists( $id, $arrobj ) { + foreach( $arrobj as $v ) + if( $v->id == $id ) return true; + return false; +} + +?> \ No newline at end of file diff --git a/include/html.php b/include/html.php new file mode 100644 index 0000000..6beb760 --- /dev/null +++ b/include/html.php @@ -0,0 +1,247 @@ + + + + + +Megatokyo Administrative Dashboard + + + + + + 'Update Statusbox', +$menus = array( + 'index.php' => 'Dashboard', + 'post-comic.php?next=yes' => 'Post Next Comic', + 'post-comic.php' => 'Post Comic', + 'post-rant.php' => 'Write Rant', + 'post-twitter.php' => 'Update Feeds', + 'post-page.php' => 'Add page', + 'manage-comics.php' => 'Manage', + 'users.php' => 'Users' + ); + +// structure of the second level menubar +$submenu['manage-comics.php'] = array( + 'manage-comics.php' => 'Comics', + 'manage-rants.php' => 'Rants' , + 'manage-pages.php' => 'Pages', + 'manage-types.php' => 'Types', + 'manage-twitter-users.php' => 'Manage Twitter Users', + 'manage-metatypes.php' => 'Metatypes', + 'swap-comics.php' => 'Swap Comics', + 'character-twitter.php' => 'Character Twitters', + 'view-adminlog.php' => 'View Admin Log' + ); + +function adminmenu($current='') { + global $menus, $submenu, $error, $info; + + if( $current ) { + $curS = $curT = $current; + } else { + $curS = $curT = basename( $_SERVER["PHP_SELF"] ); + } + + + foreach( $submenu as $l=>$L ) { + foreach( $L as $k=>$v ) { + if( $k == $curS ) { + $curT = $l; + } + } + } + $current_sub_menu = false; + + ?> +++Megatokyo Site Administration (View site »)
++ + + + + + + Deleted successfully.Howdy, . [Sign Out]
Changes saved successfully.
'; + + if( $error ) echo "$error"; + if( $info ) echo "$info"; + ?> ++ ++ + + + + +« Previous Page $page_num"; + } else { + if ( $page_num < 4 || ( $page_num >= $page - 3 && $page_num <= $page + 3 ) || $page_num > $total - 3 ) { + ?> Next Page »
+ + + ++ + + + + Megatokyo Admin
+ $title\n"; ?> + + + + $entity){ + $mapping[$entity] = '' . ord($char) . ';'; + } + return str_replace(array_keys($mapping), $mapping, $string); +} + +function utfentities($string) +{ + return htmlentities($string, ENT_COMPAT, 'UTF-8'); +} + +?> diff --git a/include/images.php b/include/images.php new file mode 100644 index 0000000..a1c41a0 --- /dev/null +++ b/include/images.php @@ -0,0 +1,57 @@ + diff --git a/include/mysql.php b/include/mysql.php new file mode 100644 index 0000000..7ba7036 --- /dev/null +++ b/include/mysql.php @@ -0,0 +1,51 @@ +link = @mysql_connect($server, $user, $pass) + or mtdie('Could not connect to the database server.'); + @mysql_select_db($dbname, $this->link) + or mtdie('Could not open the megatokyo database.'); + if( !$this->link ) mtdie('Could not connect to the database server.'); + } + + + function query($sql, $showerror = true ) { + $r = mysql_query( $sql, $this->link ); + if( false === $r && $showerror ) echo mysql_error(); + return $r; + } + + function getAll($sql) { + if( $r = $this->query( $sql ) ) { + $ret = array(); + while( $row = mysql_fetch_object( $r ) ) { + $ret[] = $row; + } + return $ret; + } + } + + function getRow($sql) { + if( $r = $this->query( $sql ) ) { + if( false === $r ) { + echo mysql_error(); + return false; + } + if( mysql_num_rows( $r ) == 0 ) return false; + return mysql_fetch_object( $r ); + } + } + + function getOne($sql) { + if( $r = $this->query( $sql ) ) { + if( mysql_num_rows( $r ) == 0 ) return false; + $ret = mysql_fetch_row( $r ); + return $ret[0]; + } + } +} + +?> \ No newline at end of file diff --git a/include/nonce.php b/include/nonce.php new file mode 100644 index 0000000..6417760 --- /dev/null +++ b/include/nonce.php @@ -0,0 +1,134 @@ +id; + + $i = ceil(time() / 600); + + //Allow for expanding range, but only do one check if we can + if( substr( md5($i . '|' . $action . $uid), -12, 10) == $nonce || substr( md5(($i - 1) . '|' . $action . $uid), -12, 10) == $nonce ) + return true; + return false; +} +endif; + +if ( !function_exists('create_nonce') ) : +function create_nonce($action = -1) { + global $currentuser; + $uid = (int) $currentuser->id; + + $i = ceil(time() / 600); + + return substr( md5($i . '|' . $action . $uid), -12, 10); +} +endif; + + +function wp_nonce_ays($action) { + if ( preg_match('#([^/]+\.php)$#', $_SERVER["PHP_SELF"], $self_matches) ) { + $pagenow = $self_matches[1]; + } elseif ( strpos($_SERVER["PHP_SELF"], '?') !== false ) { + $pagenow = explode('/', $_SERVER["PHP_SELF"]); + $pagenow = trim($pagenow[(sizeof($pagenow)-1)]); + $pagenow = explode('?', $pagenow); + $pagenow = $pagenow[0]; + } else { + $pagenow = 'index.php'; + } + + $adminurl = clean_url(wp_get_referer()); + + $html='The attempted operation is potentially unsafe.'; + + if ( $_POST ) { + $q = http_build_query($_POST); + $q = explode( ini_get('arg_separator.output'), $q); + $html .= "\t\n"; + } else { + $html .= "\t
\n"; + } + mtdie($html, 'Are You Sure?'); +} + +function mt_explain_nonce($action) { + global $mtdb; + $c = explode('-',$action); + $i = (int)$c[2]; + + $message = array(); + $message['rant']['new'] = 'Are you sure you want to create a new rant?'; + $message['rant']['save'] = 'Are you sure you want to save changes to the rant "%s"?'; + $message['rant']['delete'] = 'Are you sure you want to delete the rant "%s"? This is a destructive action, and cannot be undone!'; + + $message['type']['new'] = 'Are you sure you want to create a new type?'; + $message['type']['save'] = 'Are you sure you want to save changes to the type "%s"?'; + $message['type']['delete'] = 'Are you sure you want to delete the type "%s"? This is a destructive action, and cannot be undone!'; + + $message['metatype']['new'] = 'Are you sure you want to create a new metatype?'; + $message['metatype']['save'] = 'Are you sure you want to save changes to the metatype "%s"?'; + $message['metatype']['delete'] = 'Are you sure you want to delete the metatype "%s"? This is a destructive action, and cannot be undone!'; + + $message['strip']['new'] = 'Are you sure you want to create a new strip?'; + $message['strip']['save'] = 'Are you sure you want to save changes to the strip "%s"?'; + $message['strip']['delete'] = 'Are you sure you want to delete the comic strip "%1$s"? This will break site navigation, which can be fixed by changing other strip numbers. This is a destructive action, and cannot be undone! It is far better to edit the existing strip.'; + $message['strip']['swap'] = 'Are you sure you want to swap these two strips? This is a destructive action, and cannot be undone!'; + + $message['statusbox']['update'] = 'Are you sure you want to update the statusbox information?'; + $message['scratchpad']['new'] = 'Are you sure you want to update the scratchpad information?'; + $message['twitter']['new'] = 'Are you sure you want to update the Twitter feed?'; + + $message['extra']['new'] = 'Are you sure you want to upload a new file to /extra?'; + $message['extra']['delete'] = 'Are you sure you want to delete the file from /extra named "%s"?'; + + $message['twitteruser']['delete'] = 'Are you sure you want to delete the twitter user "%s"?'; + + if( isset( $message[ $c[1] ][ $c[0] ] )) { + $t = $message[ $c[1] ][ $c[0] ]; + + if( false !== strpos( $t, '%' ) ) { + + switch( $c[1] ) { + case 'rant': $v = $mtdb->getOne('SELECT title FROM rant WHERE id=' . $i); break; + case 'strip': $v = $mtdb->getOne('SELECT id FROM strip WHERE id=' . $i); break; + case 'type': $v = $mtdb->getOne('SELECT name FROM strip_t WHERE id=' . $i); break; + case 'metatype':$v = $mtdb->getOne('SELECT name FROM meta_t WHERE id=' . $i); break; + case 'extra': $temp = extra_file_from_inode($i); $v = $temp->name; break; + case 'twitteruser': $v = $mtdb->getOne('SELECT username FROM twitter_user WHERE id=' . $i); break; + default: $v = $i; + } + + return sprintf( $t, mt_specialchars($v,true) ); + } + + return $t; + } + return "Are you sure you want to perform the action $action?"; +} + +function nonce_field($action) { + echo ''; +} + +?> \ No newline at end of file diff --git a/include/pages.php b/include/pages.php new file mode 100644 index 0000000..2ebda84 --- /dev/null +++ b/include/pages.php @@ -0,0 +1,53 @@ +url_name) + return updatepage($page); + else + return insertpage($page); +} + +function insertpage($page) { + global $mtdb; + $sql = 'INSERT INTO static_page ( url_name, status, title, body, style ) VALUES (' + . ' "' . mysql_real_escape_string($page->url_name) + . '", "' . mysql_real_escape_string($page->status) + . '", "' . mysql_real_escape_string( trim( $page->title ) ) + . '", "' . mysql_real_escape_string( trim( $page->body ) ) + . '", "' . mysql_real_escape_string( trim( $page->style ) ) + . '")'; + adminlog("Page '".$page->url_name."' has been added.", MTS_PAGE, MTA_ADD); + return $mtdb->query($sql); +} + +function updatepage($page) { + if ( !$page->url_name ) return false; + global $mtdb; + + $sql = 'UPDATE static_page SET url_name = "' . mysql_real_escape_string($page->url_name) + . '", status = "' . mysql_real_escape_string($page->status) + . '", title = "' . mysql_real_escape_string( trim($page->title) ) + . '", body = "' . mysql_real_escape_string( trim($page->body ) ) + . '", style = "' . mysql_real_escape_string( trim($page->style ) ) + . '" WHERE url_name = "' . mysql_real_escape_string($page->url_name) . '"'; + adminlog("Page '".$page->url_name."' has been updated.", MTS_PAGE, MTA_MODIFY); + return $mtdb->query( $sql ); +} + +function deletepage($url_name) { + if ( !$url_name ) return false; + global $mtdb; + adminlog("Page '".$page->url_name."' has been deleted.", MTS_PAGE, MTA_DELETE); + return $mtdb->query( 'DELETE FROM static_page WHERE url_name = "' . mysql_real_escape_string($url_name) . '"' ); +} + +function getpage($url_name) { + global $mtdb; + return $mtdb->getRow( 'SELECT url_name, status, title, body, style FROM static_page WHERE url_name = "'. mysql_real_escape_string($url_name) . '"' ); +} + +?> diff --git a/include/rants.js b/include/rants.js new file mode 100644 index 0000000..8ea1e08 --- /dev/null +++ b/include/rants.js @@ -0,0 +1,9 @@ +function new_rant_attachment() +{ + var list = document.getElementById('rant_attachment_list'); + + var elt = document.createElement('li'); + elt.innerHTML = ''; + + list.appendChild(elt); +} diff --git a/include/rants.php b/include/rants.php new file mode 100644 index 0000000..6dca332 --- /dev/null +++ b/include/rants.php @@ -0,0 +1,119 @@ +id) + return updaterant($rant); + else + return insertrant($rant); +} + +function insertrant($rant) { + global $mtdb; + $sql = 'INSERT INTO rant ( published, status, side, author, title, body, link, imagetype, imagetext ) VALUES ( FROM_UNIXTIME(' + . (int)$rant->published + . '), "' . mysql_real_escape_string($rant->status) + . '", "' . mysql_real_escape_string($rant->side) + . '", "' . (int)$rant->author + . '", "' . mysql_real_escape_string( trim( $rant->title) ) + . '", "' . mysql_real_escape_string( trim( $rant->body ) ) + . '", "' . mysql_real_escape_string( trim( $rant->link ) ) + . '", ' . mysql_real_escape_string($rant->imagetype) + . ', "' . mysql_real_escape_string( trim( $rant->imagetext ) ) + . '")'; + + if( $mtdb->query( $sql ) ) { + //logthis( 'Saved changes to rant ' . $rant->id ); + $rant->id = mysql_insert_id( $mtdb->link ); + + adminlog("Rant ".$rant->id." saved.", MTS_RANT, MTA_ADD); + + if($rant->status == "published") + { + $poster = get_userdatabyid($rant->author); + adminlog("Rant ".$rant->id." published.", MTS_RANT, MTA_ADD); + twitterpost("New rant posted by ".$poster->name.": ".SITE_HOST.SITE_PATH."/rant/".$rant->id); + + if($rant->author === 1) { + tumblrpost($rant->title, $rant->body); + } + } + + return $rant->id; + } + return false; +} + +function updaterant($rant) { + if ( !(int)$rant->id ) return false; + global $mtdb; + + #first, check if it's published already + $qr = $mtdb->query("SELECT status FROM rant WHERE id = ".$rant->id); + $row = mysql_fetch_row($qr); + $status = $row[0]; + + adminlog("Rant ".$rant->id." updated.", MTS_RANT, MTA_UPDATE); + + $sql = 'UPDATE rant SET published=FROM_UNIXTIME(' . (int)$rant->published + . '), status = "' . mysql_real_escape_string($rant->status) + . '", side = "' . mysql_real_escape_string($rant->side) + . '", author = ' . (int)$rant->author + . ', title = "' . mysql_real_escape_string( trim($rant->title) ) + . '", body = "' . mysql_real_escape_string( trim($rant->body ) ) + . '", link = "' . mysql_real_escape_string( trim($rant->link ) ) + . '", imagetype = ' . (int)$rant->imagetype + . ', imagetext = "' . mysql_real_escape_string( trim($rant->imagetext) ) + . '" WHERE id=' . (int)$rant->id; + + if($status == "draft" && $rant->status == "published") + { + $poster = get_userdatabyid($rant->author); + adminlog("Rant ".$rant->id." published.", MTS_RANT, MTA_UPDATE); + twitterpost("New rant posted by ".$poster->name.": ".SITE_HOST.SITE_PATH."/rant/".$rant->id); + + if($rant->author === 1) { + tumblrpost($rant->title, $rant->body); + } + } + + return $mtdb->query( $sql ); +} + +function deleterant($rantid) { + if ( !(int)$rantid ) return false; + global $mtdb; + adminlog("Rant ".$rantid." deleted.", MTS_RANT, MTA_DELETE); + return $mtdb->query( 'DELETE FROM rant WHERE id=' . $rantid ); +} + +function deleteattachment($id) +{ + global $mtdb; + $file = SITE_PATH_ABS.'/'.get_rantattachment_filename($id); + unlink( $file ) or adminlog("Could not delete $file", MTS_RANT, MTA_DELETE, E_USER_WARNING); + $mtdb->query( 'DELETE FROM rant_attachment WHERE id = ' . $id ); + adminlog("Deleted attachment $id", MTS_RANT, MTA_DELETE); +} + +function getrant($id) { + global $mtdb; + return $mtdb->getRow( 'SELECT id, UNIX_TIMESTAMP(published) as published, status, side, author, title, body, link, imagetype, imagetext FROM rant WHERE id = '. (int)$id ); +} + +function get_rantimage_filename( $rant ) { + global $mtdb; + $ext = $mtdb->getOne( 'SELECT extension FROM media_t WHERE id=' . (int)$rant->imagetype ); // filename extension + return sprintf( '%s/%04d.%s',SITE_RANT, (int)$rant->id, $ext ); +} + +function get_rantattachment_filename( $id ) { + global $mtdb; + $ext = $mtdb->getOne( 'SELECT extension FROM media_t JOIN rant_attachment ra ON ra.media = media_t.id WHERE ra.id=' . (int)$id ); // filename extension + return sprintf( '%s/%d.%s',SITE_RANT_ATTACHMENT, (int)$id, $ext ); +} + +?> diff --git a/include/rss.php b/include/rss.php new file mode 100644 index 0000000..8f3e4bd --- /dev/null +++ b/include/rss.php @@ -0,0 +1,14 @@ +query('INSERT INTO rss_comment (body, url) + VALUES ("'.mysql_real_escape_string($body).'", + "'.mysql_real_escape_string($url).'")'); + + return true; +} + +?> diff --git a/include/strip.php b/include/strip.php new file mode 100644 index 0000000..414c3af --- /dev/null +++ b/include/strip.php @@ -0,0 +1,130 @@ +book = ($strip->book == '') ? 'NULL' : (int)$strip->book; + $strip->page = ($strip->page == '') ? 'NULL' : (int)$strip->page; + + $mtdb->query('START TRANSACTION'); + $newid = $mtdb->getOne('SELECT MAX(id) FROM strip') + 1; + $sql = 'INSERT INTO strip ( id, published, media, type, title, book, page ) VALUES (' + . $newid + . ', FROM_UNIXTIME(' . (int)$strip->published + . '), '. (int)$strip->media + . ', ' . (int)$strip->type + . ', "' . mysql_real_escape_string( trim($strip->title) ) + . '", '. $strip->book + . ', ' . $strip->page + . ')'; + + $r = $mtdb->query( $sql ); + if( !$r ) { + $mtdb->query('ROLLBACK'); + return false; + } + $mtdb->query('COMMIT'); + adminlog("Comic ".$newid." posted.", MTS_STRIP, MTA_ADD); + + $strip->id = $newid; + if( $strip->id == 0 ) return false; + return true; +} + +function updatestrip(&$strip) { + global $mtdb; + + $strip->book = ($strip->book === '') ? 'NULL' : (int)$strip->book; + $strip->page = ($strip->page === '') ? 'NULL' : (int)$strip->page; + + $mtdb->query('START TRANSACTION'); + $sql = 'UPDATE strip SET + published = FROM_UNIXTIME(' . (int)$strip->published .') + , media = '. (int)$strip->media .' + , type = ' . (int)$strip->type .' + , title = "' . mysql_real_escape_string( trim($strip->title) ) .'" + , book = ' . (int)$strip->book .' + , page = ' . (int)$strip->page .' + WHERE id = ' . (int)$strip->id; + $mtdb->query( $sql ); + $mtdb->query('COMMIT'); + adminlog("Comic ".$strip->id." modified.", MTS_STRIP, MTA_MODIFY); + return true; +} + +// Delete destination strip from DB and FS, and Update/Rename the source strip into place. Destructive Move! +function move_strip($from_id, $to_id) +{ + global $mtdb; + $from_id = (int) $from_id; + $to_id = (int) $to_id; + + // Ensure our source exists + $num_strips = $mtdb->getOne( "SELECT COUNT(*) FROM strip WHERE id = $from_id" ); + if($num_strips < 1) + mtdie("Cannot move strip number $from_id, because it cannot be found in database."); + + // Ready the destination + deletestrip( $to_id ); + + // Update database + $mtdb->query( "UPDATE strip SET id = $to_id WHERE id = $from_id" ); + $strip = $mtdb->getRow( "SELECT strip.id, extension FROM strip, media_t WHERE media_t.id = strip.media AND strip.id = $to_id" ); + + // Update filesystem + foreach(glob(sprintf(SITE_PATH_ABS.'/'.SITE_STRIP.'/%04d.*', $from_id)) as $item) { + preg_match('/\.(\w{3})$/', $item, $match) or die("Invalid filename: $item"); + rename($item, sprintf(SITE_PATH_ABS.'/'.SITE_STRIP.'/%04d.%s', $to_id, $match[1])); + } +} + +// Classic swap function, using strip 0 as temporary storage. Can cause concurrency issues. FLOCK! +function swap_strips( $from_id, $to_id ) { + move_strip($from_id, 0); + move_strip($to_id, $from_id); + move_strip(0, $from_id ); + adminlog("Comics ".$from_id." and ".$to_id." swapped.", MTS_STRIP, MTA_MODIFY); +} + +function deletestrip($id) { + $id = (int)$id; + if ( !$id ) return false; + + global $mtdb; + $r = $mtdb->query( 'DELETE FROM strip WHERE id=' . $id ); + foreach(glob(sprintf(SITE_PATH_ABS.'/'.SITE_STRIP.'/%04d*.*', $id)) as $item) + unlink($item); + foreach(glob(sprintf(SITE_PATH_ABS.'/'.SITE_STRIP.'/restricted/%04d*.*', $id)) as $item) + unlink($item); + adminlog("Comic ".$id." deleted.", MTS_STRIP, MTA_DELETE); + return $r; +} + +function getstrip($id) { + global $mtdb; + return $mtdb->getRow( 'SELECT id, UNIX_TIMESTAMP(published) as published, type, media, title, book, page FROM strip WHERE id=' . (int)$id); +} + +function get_stripimage_filename( $strip ) { + global $mtdb; + $ext = $mtdb->getOne( 'SELECT extension FROM media_t WHERE id=' . (int)$strip->media ); // filename extension + return sprintf( '%s/%04d.%s', SITE_STRIP, $strip->id, $ext ); +} + +function get_stripid_by_rantid($rantid) { + global $mtdb; + return $mtdb->getOne('SELECT MAX(strip.id) FROM strip,rant WHERE strip.published<=rant.published AND rant.id=' . (int)$rantid); +} + +?> diff --git a/include/transcript.php b/include/transcript.php new file mode 100644 index 0000000..4493cf7 --- /dev/null +++ b/include/transcript.php @@ -0,0 +1,182 @@ +") == 0) + return true; + + if(substr_count($line, "<") != substr_count($line, ">")) + { + return false; + } + + return true; +} + +// Retrieve transcript for this strip from the database, modifying the strip object. +function gettranscript(&$strip) +{ + global $mtdb; + + $result = $mtdb->query('SELECT strip FROM transcript WHERE strip=' . (int)$strip->id ); + + if($result) + { + $output = ''; + /* + #this gets me the highest panel number that has a transcript + # panels beyond that either lack speech or don't exist + # either way, I care not + Might be able to exchange this loop of getOne()s for a getAll() call. + */ + $numPanels = $mtdb->getOne( 'SELECT MAX(panel) FROM transcript WHERE transcript.strip=' . (int)$strip->id ); + if( $numPanels ) { + for($i = 1; $i <= $numPanels; $i++) { + $result = $mtdb->query( 'SELECT speaker, speech FROM transcript WHERE transcript.strip=' . (int)$strip->id . ' AND panel=' .$i.' ORDER BY line') + or mtdie("There was an error fetching the panel count in the transcript for $strip->id, panel $i. " . mysql_error(), 'SQL Error'); + + if(!$result) continue; + + $output.= "\nnewpanel\n"; + while($row = mysql_fetch_row($result)) { + if(strlen($row[0]) < 1) continue; + + $output.= $row[0]; + if($row[1] !== '') $output.= ":: ".$row[1]; + $output.= "\n"; + } + } + } + } + $strip->transcript = $output; + return $output; +} + + +// Parse submitted transcript from strip object, and insert it into the database. +function savetranscript( &$strip ) { + global $mtdb; + $info = ''; + + $mtdb->query('START TRANSACTION'); + + //remove any old transcript data - it's being replaced + $mtdb->query( 'DELETE FROM transcript WHERE transcript.strip=' . (int)$strip->id ); + + if( $strip->transcript_posted ) { + $inserter = 'INSERT INTO transcript (strip,panel,line,speaker,speech,search) VALUES (%d,%d,%d,"%s","%s","%s")'; + + if(strpos($strip->transcript_posted, 'Panel <$n>') !== FALSE) { + # This is probably a scrivener script + + $panels = explode('Panel <$n>', $strip->transcript_posted); + $panels = array_map('trim', $panels); + $numPanels = count($panels); + + for($i = 0; $i < $numPanels; $i++) { + $lines = explode("\n", $panels[$i]); + $lines = array_map('trim', $lines); + + # Initialize speaker controls + $speaker = null; + $has_spoken = true; + + for($j = 0; $j < count($lines); $j++) { + $insert_sql = ''; + + if(strpos($lines[$j], '(') === 0) { + # Line is a note, add it as a comment + $insert_sql = sprintf($inserter, (int)$strip->id, $i, $j, '#', mysql_real_escape_string($lines[$j]), ''); + } elseif(strpos($lines[$j], '[') === 0 || strlen($lines[$j]) == 0) { + # Line is an annotation or blank, do nothing + continue; + } elseif(strpos($lines[$j], 'nospeaking:') === 0) { + # Line contains a list of nonspeaking characters + array_splice($lines, $j, 1, array_map('_nospeaker', explode(',', substr($lines[$j], 11)))); + $speaker = trim(substr($lines[$j], 11)); + $insert_sql = sprintf($inserter, (int)$strip->id, $i, $j, mysql_real_escape_string($speaker), '', ''); + } elseif($i > 0 && $lines[$j] == strtoupper($lines[$j])) { + # Line designates a new speaker, note speaker + + # Handle speakers who did not say anything + if(null !== $speaker && !$has_spoken) + $insert_sql = sprintf($inserter, (int)$strip->id, $i, $j, mysql_real_escape_string($speaker), '', ''); + + $speaker = ucfirst(strtolower($lines[$j])); + $has_spoken = false; + } elseif(null !== $speaker) { + # Line is speech, match with speaker and add + if(!bracketbalance($lines[$j])) + $info .= "
Warning: Open brackets do not match close brackets in panel $i for speaker ".htmlentities($speaker).'
'; + + $search = preg_replace( '/[[:punct:]]|(?<=\s)\s+/', ' ', strtolower($lines[$j]) ); + $insert_sql = sprintf($inserter, (int)$strip->id, $i, $j, mysql_real_escape_string($speaker), + mysql_real_escape_string($lines[$j]), mysql_real_escape_string($search)); + $has_spoken = true; + } else { + # Line is unrecognized, add it as a comment + $insert_sql = sprintf($inserter, (int)$strip->id, $i, $j, '#', mysql_real_escape_string($lines[$j]), ''); + } + + if( $insert_sql && false === $mtdb->query( $insert_sql ) ) { + $mtdb->query('ROLLBACK'); + mtdie (mysql_error(), 'Error inserting transcript.'); + } + } + + if(null !== $speaker && !$has_spoken) { + $insert_sql = sprintf($inserter, (int)$strip->id, $i, $j, mysql_real_escape_string($speaker), '', ''); + if( false === $mtdb->query( $insert_sql ) ) { + $mtdb->query('ROLLBACK'); + mtdie (mysql_error(), 'Error inserting transcript.'); + } + } + } + + } else { + # Assume that this is a Kalium style transcript + + $panels = explode('newpanel', $strip->transcript_posted); + $numPanels = count($panels); + foreach($panels as $currPanel) + $currPanel = trim($currPanel); + + for($i = 1; $i < $numPanels; $i++) { + $lines = explode("\n", $panels[$i]); + $numLines = count($lines); + foreach($lines as $currLine) + $currLine = trim($currLine); + + for($j = 1; $j < $numLines; $j++) { + $spoken = explode("::", $lines[$j]); // Distinguish between speaker and speech + + $spoken[0] = trim($spoken[0]); // Strip excess whitespace + $spoken[1] = trim($spoken[1]); + + if(strlen($spoken[0]) < 1) continue; // Disregard null + $spoken[2] = preg_replace('/[[:punct:]]|(?<=\s)\s+/', ' ', strtolower($spoken[1]) ); // Make searchable text + + if(!bracketbalance($spoken[1])) + $info .= "Warning: Open brackets do not match close brackets in panel $i for speaker ".htmlentities($spoken[0]).'
'; + + $insert_sql = sprintf($inserter, (int)$strip->id, (int)$i, (int)$j, mysql_real_escape_string($spoken[0]), + mysql_real_escape_string($spoken[1]), mysql_real_escape_string($spoken[2]) ); + if( false === $mtdb->query( $insert_sql ) ) { + $mtdb->query('ROLLBACK'); + mtdie (mysql_error(), 'Error inserting transcript.'); + } + } + } + } + } + $mtdb->query('COMMIT'); + return $info; +} + +function _nospeaker($name) +{ + return "nospeaking: $name"; +} + +?> diff --git a/include/tumblr.php b/include/tumblr.php new file mode 100644 index 0000000..5f54710 --- /dev/null +++ b/include/tumblr.php @@ -0,0 +1,50 @@ + $tumblr_email, + 'password' => $tumblr_password, + 'type' => $post_type, + 'title' => $post_title, + 'body' => $post_body, + 'generator' => 'Megatokyo', + 'format' => 'html', + 'tags' => 'rant', + ) + ); + + // Send the POST request (with cURL) + $c = curl_init('http://www.tumblr.com/api/write'); + curl_setopt($c, CURLOPT_POST, true); + curl_setopt($c, CURLOPT_POSTFIELDS, $request_data); + curl_setopt($c, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($c); + $status = curl_getinfo($c, CURLINFO_HTTP_CODE); + curl_close($c); + + // Check for success + if ($status == 201) { + $info .= "Successfully posted to Tumblr.
"; + adminlog("Post to Tumblr successful. Post id $result", MTS_TUMBLR, MTA_ADD); + } else if ($status == 403) { + $error .= 'Bad email or password posting to Tumblr.
'; + adminlog('Bad email or password posting to Tumblr.', MTS_TUMBLR, MTA_ADD); + } else { + $error .= "There was an error posting to Tumblr.
"; + adminlog("Error posting to Tumblr: $result", MTS_TUMBLR, MTA_ADD); + } +} + +?> diff --git a/include/twitter.php b/include/twitter.php new file mode 100644 index 0000000..b6816ba --- /dev/null +++ b/include/twitter.php @@ -0,0 +1,63 @@ +getRow( sprintf('SELECT id, username, oauth_token, oauth_token_secret FROM twitter_user WHERE username="%s"', mysql_real_escape_string($user))); + + $username = $row->username; + $oauth_token = $row->oauth_token; + $oauth_token_secret = $row->oauth_token_secret; + + $connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $oauth_token, $oauth_token_secret); + + $parameters = array('status' => $message ); + $status = $connection->post('statuses/update', $parameters); + + switch( $connection->http_code ) { + case 200: + adminlog("Twitter post succeeded for user $username!", MTS_TWITTER, MTA_ADD); + return true; + default: + adminlog("Twitter post failed for user $username!", MTS_TWITTER, MTA_ADD); + return false; + } + + + } + + +} + + +function setOAuthTokens($userid,$oauth_token,$oauth_token_secret, $username) { + global $mtdb; + $id = (int)$userid; + if( $mtdb->query( sprintf('UPDATE twitter_user SET oauth_token="%s", oauth_token_secret="%s", username="%s" WHERE id=%d', mysql_real_escape_string($oauth_token), mysql_real_escape_string($oauth_token_secret), mysql_real_escape_string($username), $id )) ) + return true; + return false; +} + +?> diff --git a/include/twitteroauth/OAuth.php b/include/twitteroauth/OAuth.php new file mode 100644 index 0000000..67a94c4 --- /dev/null +++ b/include/twitteroauth/OAuth.php @@ -0,0 +1,874 @@ +key = $key; + $this->secret = $secret; + $this->callback_url = $callback_url; + } + + function __toString() { + return "OAuthConsumer[key=$this->key,secret=$this->secret]"; + } +} + +class OAuthToken { + // access tokens and request tokens + public $key; + public $secret; + + /** + * key = the token + * secret = the token secret + */ + function __construct($key, $secret) { + $this->key = $key; + $this->secret = $secret; + } + + /** + * generates the basic string serialization of a token that a server + * would respond to request_token and access_token calls with + */ + function to_string() { + return "oauth_token=" . + OAuthUtil::urlencode_rfc3986($this->key) . + "&oauth_token_secret=" . + OAuthUtil::urlencode_rfc3986($this->secret); + } + + function __toString() { + return $this->to_string(); + } +} + +/** + * A class for implementing a Signature Method + * See section 9 ("Signing Requests") in the spec + */ +abstract class OAuthSignatureMethod { + /** + * Needs to return the name of the Signature Method (ie HMAC-SHA1) + * @return string + */ + abstract public function get_name(); + + /** + * Build up the signature + * NOTE: The output of this function MUST NOT be urlencoded. + * the encoding is handled in OAuthRequest when the final + * request is serialized + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @return string + */ + abstract public function build_signature($request, $consumer, $token); + + /** + * Verifies that a given signature is correct + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @param string $signature + * @return bool + */ + public function check_signature($request, $consumer, $token, $signature) { + $built = $this->build_signature($request, $consumer, $token); + return $built == $signature; + } +} + +/** + * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] + * where the Signature Base String is the text and the key is the concatenated values (each first + * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' + * character (ASCII code 38) even if empty. + * - Chapter 9.2 ("HMAC-SHA1") + */ +class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { + function get_name() { + return "HMAC-SHA1"; + } + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + + return base64_encode(hash_hmac('sha1', $base_string, $key, true)); + } +} + +/** + * The PLAINTEXT method does not provide any security protection and SHOULD only be used + * over a secure channel such as HTTPS. It does not use the Signature Base String. + * - Chapter 9.4 ("PLAINTEXT") + */ +class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { + public function get_name() { + return "PLAINTEXT"; + } + + /** + * oauth_signature is set to the concatenated encoded values of the Consumer Secret and + * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is + * empty. The result MUST be encoded again. + * - Chapter 9.4.1 ("Generating Signatures") + * + * Please note that the second encoding MUST NOT happen in the SignatureMethod, as + * OAuthRequest handles this! + */ + public function build_signature($request, $consumer, $token) { + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + $request->base_string = $key; + + return $key; + } +} + +/** + * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in + * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for + * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a + * verified way to the Service Provider, in a manner which is beyond the scope of this + * specification. + * - Chapter 9.3 ("RSA-SHA1") + */ +abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod { + public function get_name() { + return "RSA-SHA1"; + } + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // (2) fetch via http using a url provided by the requester + // (3) some sort of specific discovery code based on request + // + // Either way should return a string representation of the certificate + protected abstract function fetch_public_cert(&$request); + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // + // Either way should return a string representation of the certificate + protected abstract function fetch_private_cert(&$request); + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + // Fetch the private key cert based on the request + $cert = $this->fetch_private_cert($request); + + // Pull the private key ID from the certificate + $privatekeyid = openssl_get_privatekey($cert); + + // Sign using the key + $ok = openssl_sign($base_string, $signature, $privatekeyid); + + // Release the key resource + openssl_free_key($privatekeyid); + + return base64_encode($signature); + } + + public function check_signature($request, $consumer, $token, $signature) { + $decoded_sig = base64_decode($signature); + + $base_string = $request->get_signature_base_string(); + + // Fetch the public key cert based on the request + $cert = $this->fetch_public_cert($request); + + // Pull the public key ID from the certificate + $publickeyid = openssl_get_publickey($cert); + + // Check the computed signature against the one passed in the query + $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); + + // Release the key resource + openssl_free_key($publickeyid); + + return $ok == 1; + } +} + +class OAuthRequest { + private $parameters; + private $http_method; + private $http_url; + // for debug purposes + public $base_string; + public static $version = '1.0'; + public static $POST_INPUT = 'php://input'; + + function __construct($http_method, $http_url, $parameters=NULL) { + @$parameters or $parameters = array(); + $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); + $this->parameters = $parameters; + $this->http_method = $http_method; + $this->http_url = $http_url; + } + + + /** + * attempt to build up a request from what was passed to the server + */ + public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) { + $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") + ? 'http' + : 'https'; + @$http_url or $http_url = $scheme . + '://' . $_SERVER['HTTP_HOST'] . + ':' . + $_SERVER['SERVER_PORT'] . + $_SERVER['REQUEST_URI']; + @$http_method or $http_method = $_SERVER['REQUEST_METHOD']; + + // We weren't handed any parameters, so let's find the ones relevant to + // this request. + // If you run XML-RPC or similar you should use this to provide your own + // parsed parameter-list + if (!$parameters) { + // Find request headers + $request_headers = OAuthUtil::get_headers(); + + // Parse the query-string to find GET parameters + $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); + + // It's a POST request of the proper content-type, so parse POST + // parameters and add those overriding any duplicates from GET + if ($http_method == "POST" + && @strstr($request_headers["Content-Type"], + "application/x-www-form-urlencoded") + ) { + $post_data = OAuthUtil::parse_parameters( + file_get_contents(self::$POST_INPUT) + ); + $parameters = array_merge($parameters, $post_data); + } + + // We have a Authorization-header with OAuth data. Parse the header + // and add those overriding any duplicates from GET or POST + if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") { + $header_parameters = OAuthUtil::split_header( + $request_headers['Authorization'] + ); + $parameters = array_merge($parameters, $header_parameters); + } + + } + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + /** + * pretty much a helper function to set up the request + */ + public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) { + @$parameters or $parameters = array(); + $defaults = array("oauth_version" => OAuthRequest::$version, + "oauth_nonce" => OAuthRequest::generate_nonce(), + "oauth_timestamp" => OAuthRequest::generate_timestamp(), + "oauth_consumer_key" => $consumer->key); + if ($token) + $defaults['oauth_token'] = $token->key; + + $parameters = array_merge($defaults, $parameters); + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + public function set_parameter($name, $value, $allow_duplicates = true) { + if ($allow_duplicates && isset($this->parameters[$name])) { + // We have already added parameter(s) with this name, so add to the list + if (is_scalar($this->parameters[$name])) { + // This is the first duplicate, so transform scalar (string) + // into an array so we can add the duplicates + $this->parameters[$name] = array($this->parameters[$name]); + } + + $this->parameters[$name][] = $value; + } else { + $this->parameters[$name] = $value; + } + } + + public function get_parameter($name) { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + public function get_parameters() { + return $this->parameters; + } + + public function unset_parameter($name) { + unset($this->parameters[$name]); + } + + /** + * The request parameters, sorted and concatenated into a normalized string. + * @return string + */ + public function get_signable_parameters() { + // Grab all parameters + $params = $this->parameters; + + // Remove oauth_signature if present + // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") + if (isset($params['oauth_signature'])) { + unset($params['oauth_signature']); + } + + return OAuthUtil::build_http_query($params); + } + + /** + * Returns the base string of this request + * + * The base string defined as the method, the url + * and the parameters (normalized), each urlencoded + * and the concated with &. + */ + public function get_signature_base_string() { + $parts = array( + $this->get_normalized_http_method(), + $this->get_normalized_http_url(), + $this->get_signable_parameters() + ); + + $parts = OAuthUtil::urlencode_rfc3986($parts); + + return implode('&', $parts); + } + + /** + * just uppercases the http method + */ + public function get_normalized_http_method() { + return strtoupper($this->http_method); + } + + /** + * parses the url and rebuilds it to be + * scheme://host/path + */ + public function get_normalized_http_url() { + $parts = parse_url($this->http_url); + + $port = @$parts['port']; + $scheme = $parts['scheme']; + $host = $parts['host']; + $path = @$parts['path']; + + $port or $port = ($scheme == 'https') ? '443' : '80'; + + if (($scheme == 'https' && $port != '443') + || ($scheme == 'http' && $port != '80')) { + $host = "$host:$port"; + } + return "$scheme://$host$path"; + } + + /** + * builds a url usable for a GET request + */ + public function to_url() { + $post_data = $this->to_postdata(); + $out = $this->get_normalized_http_url(); + if ($post_data) { + $out .= '?'.$post_data; + } + return $out; + } + + /** + * builds the data one would send in a POST request + */ + public function to_postdata() { + return OAuthUtil::build_http_query($this->parameters); + } + + /** + * builds the Authorization: header + */ + public function to_header($realm=null) { + $first = true; + if($realm) { + $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; + $first = false; + } else + $out = 'Authorization: OAuth'; + + $total = array(); + foreach ($this->parameters as $k => $v) { + if (substr($k, 0, 5) != "oauth") continue; + if (is_array($v)) { + throw new OAuthException('Arrays not supported in headers'); + } + $out .= ($first) ? ' ' : ','; + $out .= OAuthUtil::urlencode_rfc3986($k) . + '="' . + OAuthUtil::urlencode_rfc3986($v) . + '"'; + $first = false; + } + return $out; + } + + public function __toString() { + return $this->to_url(); + } + + + public function sign_request($signature_method, $consumer, $token) { + $this->set_parameter( + "oauth_signature_method", + $signature_method->get_name(), + false + ); + $signature = $this->build_signature($signature_method, $consumer, $token); + $this->set_parameter("oauth_signature", $signature, false); + } + + public function build_signature($signature_method, $consumer, $token) { + $signature = $signature_method->build_signature($this, $consumer, $token); + return $signature; + } + + /** + * util function: current timestamp + */ + private static function generate_timestamp() { + return time(); + } + + /** + * util function: current nonce + */ + private static function generate_nonce() { + $mt = microtime(); + $rand = mt_rand(); + + return md5($mt . $rand); // md5s look nicer than numbers + } +} + +class OAuthServer { + protected $timestamp_threshold = 300; // in seconds, five minutes + protected $version = '1.0'; // hi blaine + protected $signature_methods = array(); + + protected $data_store; + + function __construct($data_store) { + $this->data_store = $data_store; + } + + public function add_signature_method($signature_method) { + $this->signature_methods[$signature_method->get_name()] = + $signature_method; + } + + // high level functions + + /** + * process a request_token request + * returns the request token on success + */ + public function fetch_request_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // no token required for the initial token request + $token = NULL; + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $callback = $request->get_parameter('oauth_callback'); + $new_token = $this->data_store->new_request_token($consumer, $callback); + + return $new_token; + } + + /** + * process an access_token request + * returns the access token on success + */ + public function fetch_access_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // requires authorized request token + $token = $this->get_token($request, $consumer, "request"); + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $verifier = $request->get_parameter('oauth_verifier'); + $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); + + return $new_token; + } + + /** + * verify an api call, checks all the parameters + */ + public function verify_request(&$request) { + $this->get_version($request); + $consumer = $this->get_consumer($request); + $token = $this->get_token($request, $consumer, "access"); + $this->check_signature($request, $consumer, $token); + return array($consumer, $token); + } + + // Internals from here + /** + * version 1 + */ + private function get_version(&$request) { + $version = $request->get_parameter("oauth_version"); + if (!$version) { + // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. + // Chapter 7.0 ("Accessing Protected Ressources") + $version = '1.0'; + } + if ($version !== $this->version) { + throw new OAuthException("OAuth version '$version' not supported"); + } + return $version; + } + + /** + * figure out the signature with some defaults + */ + private function get_signature_method(&$request) { + $signature_method = + @$request->get_parameter("oauth_signature_method"); + + if (!$signature_method) { + // According to chapter 7 ("Accessing Protected Ressources") the signature-method + // parameter is required, and we can't just fallback to PLAINTEXT + throw new OAuthException('No signature method parameter. This parameter is required'); + } + + if (!in_array($signature_method, + array_keys($this->signature_methods))) { + throw new OAuthException( + "Signature method '$signature_method' not supported " . + "try one of the following: " . + implode(", ", array_keys($this->signature_methods)) + ); + } + return $this->signature_methods[$signature_method]; + } + + /** + * try to find the consumer for the provided request's consumer key + */ + private function get_consumer(&$request) { + $consumer_key = @$request->get_parameter("oauth_consumer_key"); + if (!$consumer_key) { + throw new OAuthException("Invalid consumer key"); + } + + $consumer = $this->data_store->lookup_consumer($consumer_key); + if (!$consumer) { + throw new OAuthException("Invalid consumer"); + } + + return $consumer; + } + + /** + * try to find the token for the provided request's token key + */ + private function get_token(&$request, $consumer, $token_type="access") { + $token_field = @$request->get_parameter('oauth_token'); + $token = $this->data_store->lookup_token( + $consumer, $token_type, $token_field + ); + if (!$token) { + throw new OAuthException("Invalid $token_type token: $token_field"); + } + return $token; + } + + /** + * all-in-one function to check the signature on a request + * should guess the signature method appropriately + */ + private function check_signature(&$request, $consumer, $token) { + // this should probably be in a different method + $timestamp = @$request->get_parameter('oauth_timestamp'); + $nonce = @$request->get_parameter('oauth_nonce'); + + $this->check_timestamp($timestamp); + $this->check_nonce($consumer, $token, $nonce, $timestamp); + + $signature_method = $this->get_signature_method($request); + + $signature = $request->get_parameter('oauth_signature'); + $valid_sig = $signature_method->check_signature( + $request, + $consumer, + $token, + $signature + ); + + if (!$valid_sig) { + throw new OAuthException("Invalid signature"); + } + } + + /** + * check that the timestamp is new enough + */ + private function check_timestamp($timestamp) { + if( ! $timestamp ) + throw new OAuthException( + 'Missing timestamp parameter. The parameter is required' + ); + + // verify that timestamp is recentish + $now = time(); + if (abs($now - $timestamp) > $this->timestamp_threshold) { + throw new OAuthException( + "Expired timestamp, yours $timestamp, ours $now" + ); + } + } + + /** + * check that the nonce is not repeated + */ + private function check_nonce($consumer, $token, $nonce, $timestamp) { + if( ! $nonce ) + throw new OAuthException( + 'Missing nonce parameter. The parameter is required' + ); + + // verify that the nonce is uniqueish + $found = $this->data_store->lookup_nonce( + $consumer, + $token, + $nonce, + $timestamp + ); + if ($found) { + throw new OAuthException("Nonce already used: $nonce"); + } + } + +} + +class OAuthDataStore { + function lookup_consumer($consumer_key) { + // implement me + } + + function lookup_token($consumer, $token_type, $token) { + // implement me + } + + function lookup_nonce($consumer, $token, $nonce, $timestamp) { + // implement me + } + + function new_request_token($consumer, $callback = null) { + // return a new token attached to this consumer + } + + function new_access_token($token, $consumer, $verifier = null) { + // return a new access token attached to this consumer + // for the user associated with this token if the request token + // is authorized + // should also invalidate the request token + } + +} + +class OAuthUtil { + public static function urlencode_rfc3986($input) { + if (is_array($input)) { + return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input); + } else if (is_scalar($input)) { + return str_replace( + '+', + ' ', + str_replace('%7E', '~', rawurlencode($input)) + ); + } else { + return ''; + } +} + + + // This decode function isn't taking into consideration the above + // modifications to the encoding process. However, this method doesn't + // seem to be used anywhere so leaving it as is. + public static function urldecode_rfc3986($string) { + return urldecode($string); + } + + // Utility function for turning the Authorization: header into + // parameters, has to do some unescaping + // Can filter out any non-oauth parameters if needed (default behaviour) + public static function split_header($header, $only_allow_oauth_parameters = true) { + $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/'; + $offset = 0; + $params = array(); + while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) { + $match = $matches[0]; + $header_name = $matches[2][0]; + $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0]; + if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) { + $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content); + } + $offset = $match[1] + strlen($match[0]); + } + + if (isset($params['realm'])) { + unset($params['realm']); + } + + return $params; + } + + // helper to try to sort out headers for people who aren't running apache + public static function get_headers() { + if (function_exists('apache_request_headers')) { + // we need this to get the actual Authorization: header + // because apache tends to tell us it doesn't exist + $headers = apache_request_headers(); + + // sanitize the output of apache_request_headers because + // we always want the keys to be Cased-Like-This and arh() + // returns the headers in the same case as they are in the + // request + $out = array(); + foreach( $headers AS $key => $value ) { + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("-", " ", $key))) + ); + $out[$key] = $value; + } + } else { + // otherwise we don't have apache and are just going to have to hope + // that $_SERVER actually contains what we need + $out = array(); + if( isset($_SERVER['CONTENT_TYPE']) ) + $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; + if( isset($_ENV['CONTENT_TYPE']) ) + $out['Content-Type'] = $_ENV['CONTENT_TYPE']; + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) == "HTTP_") { + // this is chaos, basically it is just there to capitalize the first + // letter of every word that is not an initial HTTP and strip HTTP + // code from przemek + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) + ); + $out[$key] = $value; + } + } + } + return $out; + } + + // This function takes a input like a=b&a=c&d=e and returns the parsed + // parameters like this + // array('a' => array('b','c'), 'd' => 'e') + public static function parse_parameters( $input ) { + if (!isset($input) || !$input) return array(); + + $pairs = explode('&', $input); + + $parsed_parameters = array(); + foreach ($pairs as $pair) { + $split = explode('=', $pair, 2); + $parameter = OAuthUtil::urldecode_rfc3986($split[0]); + $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; + + if (isset($parsed_parameters[$parameter])) { + // We have already recieved parameter(s) with this name, so add to the list + // of parameters with this name + + if (is_scalar($parsed_parameters[$parameter])) { + // This is the first duplicate, so transform scalar (string) into an array + // so we can add the duplicates + $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); + } + + $parsed_parameters[$parameter][] = $value; + } else { + $parsed_parameters[$parameter] = $value; + } + } + return $parsed_parameters; + } + + public static function build_http_query($params) { + if (!$params) return ''; + + // Urlencode both keys and values + $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); + $values = OAuthUtil::urlencode_rfc3986(array_values($params)); + $params = array_combine($keys, $values); + + // Parameters are sorted by name, using lexicographical byte value ordering. + // Ref: Spec: 9.1.1 (1) + uksort($params, 'strcmp'); + + $pairs = array(); + foreach ($params as $parameter => $value) { + if (is_array($value)) { + // If two or more parameters share the same name, they are sorted by their value + // Ref: Spec: 9.1.1 (1) + natsort($value); + foreach ($value as $duplicate_value) { + $pairs[] = $parameter . '=' . $duplicate_value; + } + } else { + $pairs[] = $parameter . '=' . $value; + } + } + // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) + // Each name-value pair is separated by an '&' character (ASCII code 38) + return implode('&', $pairs); + } +} + +?> diff --git a/include/twitteroauth/twitteroauth-old.php b/include/twitteroauth/twitteroauth-old.php new file mode 100644 index 0000000..674308a --- /dev/null +++ b/include/twitteroauth/twitteroauth-old.php @@ -0,0 +1,245 @@ +http_status; } + function lastAPICall() { return $this->last_api_call; } + + /** + * construct TwitterOAuth object + */ + function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) { + $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); + $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); + if (!empty($oauth_token) && !empty($oauth_token_secret)) { + $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret); + } else { + $this->token = NULL; + } + } + + + /** + * Get a request_token from Twitter + * + * @returns a key/value array containing oauth_token and oauth_token_secret + */ + function getRequestToken($oauth_callback = NULL) { + $parameters = array(); + if (!empty($oauth_callback)) { + $parameters['oauth_callback'] = $oauth_callback; + } + $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters); + $token = OAuthUtil::parse_parameters($request); + $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); + return $token; + } + + /** + * Get the authorize URL + * + * @returns a string + */ + function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) { + if (is_array($token)) { + $token = $token['oauth_token']; + } + if (empty($sign_in_with_twitter)) { + return $this->authorizeURL() . "?oauth_token={$token}"; + } else { + return $this->authenticateURL() . "?oauth_token={$token}"; + } + } + + /** + * Exchange request token and secret for an access token and + * secret, to sign API calls. + * + * @returns array("oauth_token" => "the-access-token", + * "oauth_token_secret" => "the-access-secret", + * "user_id" => "9436992", + * "screen_name" => "abraham") + */ + function getAccessToken($oauth_verifier = FALSE) { + $parameters = array(); + if (!empty($oauth_verifier)) { + $parameters['oauth_verifier'] = $oauth_verifier; + } + $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters); + $token = OAuthUtil::parse_parameters($request); + $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); + return $token; + } + + /** + * One time exchange of username and password for access token and secret. + * + * @returns array("oauth_token" => "the-access-token", + * "oauth_token_secret" => "the-access-secret", + * "user_id" => "9436992", + * "screen_name" => "abraham", + * "x_auth_expires" => "0") + */ + function getXAuthToken($username, $password) { + $parameters = array(); + $parameters['x_auth_username'] = $username; + $parameters['x_auth_password'] = $password; + $parameters['x_auth_mode'] = 'client_auth'; + $request = $this->oAuthRequest($this->accessTokenURL(), 'POST', $parameters); + $token = OAuthUtil::parse_parameters($request); + $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); + return $token; + } + + /** + * GET wrapper for oAuthRequest. + */ + function get($url, $parameters = array()) { + $response = $this->oAuthRequest($url, 'GET', $parameters); + if ($this->format === 'json' && $this->decode_json) { + return json_decode($response); + } + return $response; + } + + /** + * POST wrapper for oAuthRequest. + */ + function post($url, $parameters = array()) { + $response = $this->oAuthRequest($url, 'POST', $parameters); + if ($this->format === 'json' && $this->decode_json) { + return json_decode($response); + } + return $response; + } + + /** + * DELETE wrapper for oAuthReqeust. + */ + function delete($url, $parameters = array()) { + $response = $this->oAuthRequest($url, 'DELETE', $parameters); + if ($this->format === 'json' && $this->decode_json) { + return json_decode($response); + } + return $response; + } + + /** + * Format and sign an OAuth / API request + */ + function oAuthRequest($url, $method, $parameters) { + if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) { + $url = "{$this->host}{$url}.{$this->format}"; + } + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters); + $request->sign_request($this->sha1_method, $this->consumer, $this->token); + switch ($method) { + case 'GET': + return $this->http($request->to_url(), 'GET'); + default: + return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata()); + } + } + + /** + * Make an HTTP request + * + * @return API results + */ + function http($url, $method, $postfields = NULL) { + $this->http_info = array(); + $ci = curl_init(); + /* Curl settings */ + curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent); + curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout); + curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:')); + curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer); + curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader')); + curl_setopt($ci, CURLOPT_HEADER, FALSE); + + switch ($method) { + case 'POST': + curl_setopt($ci, CURLOPT_POST, TRUE); + if (!empty($postfields)) { + curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields); + } + break; + case 'DELETE': + curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE'); + if (!empty($postfields)) { + $url = "{$url}?{$postfields}"; + } + } + + curl_setopt($ci, CURLOPT_URL, $url); + $response = curl_exec($ci); + $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); + $this->url = $url; + curl_close ($ci); + return $response; + } + + /** + * Get the header info to store. + */ + function getHeader($ch, $header) { + $i = strpos($header, ':'); + if (!empty($i)) { + $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); + $value = trim(substr($header, $i + 2)); + $this->http_header[$key] = $value; + } + return strlen($header); + } +} diff --git a/include/twitteroauth/twitteroauth.php b/include/twitteroauth/twitteroauth.php new file mode 100644 index 0000000..0fdc135 --- /dev/null +++ b/include/twitteroauth/twitteroauth.php @@ -0,0 +1,246 @@ +http_status; } + function lastAPICall() { return $this->last_api_call; } + + /** + * construct TwitterOAuth object + */ + function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) { + $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); + $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); + if (!empty($oauth_token) && !empty($oauth_token_secret)) { + $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret); + } else { + $this->token = NULL; + } + } + + + /** + * Get a request_token from Twitter + * + * @returns a key/value array containing oauth_token and oauth_token_secret + */ + function getRequestToken($oauth_callback = NULL) { + $parameters = array(); + if (!empty($oauth_callback)) { + $parameters['oauth_callback'] = $oauth_callback; + } + $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters); + $token = OAuthUtil::parse_parameters($request); + $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); + return $token; + } + + /** + * Get the authorize URL + * + * @returns a string + */ + function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) { + if (is_array($token)) { + $token = $token['oauth_token']; + } + if (empty($sign_in_with_twitter)) { + return $this->authorizeURL() . "?oauth_token={$token}"; + } else { + return $this->authenticateURL() . "?oauth_token={$token}"; + } + } + + /** + * Exchange request token and secret for an access token and + * secret, to sign API calls. + * + * @returns array("oauth_token" => "the-access-token", + * "oauth_token_secret" => "the-access-secret", + * "user_id" => "9436992", + * "screen_name" => "abraham") + */ + function getAccessToken($oauth_verifier = FALSE) { + $parameters = array(); + if (!empty($oauth_verifier)) { + $parameters['oauth_verifier'] = $oauth_verifier; + } + $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters); + $token = OAuthUtil::parse_parameters($request); + $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); + return $token; + } + + /** + * One time exchange of username and password for access token and secret. + * + * @returns array("oauth_token" => "the-access-token", + * "oauth_token_secret" => "the-access-secret", + * "user_id" => "9436992", + * "screen_name" => "abraham", + * "x_auth_expires" => "0") + */ + function getXAuthToken($username, $password) { + $parameters = array(); + $parameters['x_auth_username'] = $username; + $parameters['x_auth_password'] = $password; + $parameters['x_auth_mode'] = 'client_auth'; + $request = $this->oAuthRequest($this->accessTokenURL(), 'POST', $parameters); + $token = OAuthUtil::parse_parameters($request); + $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); + return $token; + } + + /** + * GET wrapper for oAuthRequest. + */ + function get($url, $parameters = array()) { + $response = $this->oAuthRequest($url, 'GET', $parameters); + if ($this->format === 'json' && $this->decode_json) { + return json_decode($response); + } + return $response; + } + + /** + * POST wrapper for oAuthRequest. + */ + function post($url, $parameters = array()) { + $response = $this->oAuthRequest($url, 'POST', $parameters); + if ($this->format === 'json' && $this->decode_json) { + return json_decode($response); + } + return $response; + } + + /** + * DELETE wrapper for oAuthReqeust. + */ + function delete($url, $parameters = array()) { + $response = $this->oAuthRequest($url, 'DELETE', $parameters); + if ($this->format === 'json' && $this->decode_json) { + return json_decode($response); + } + return $response; + } + + /** + * Format and sign an OAuth / API request + */ + function oAuthRequest($url, $method, $parameters) { + if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) { + $url = "{$this->host}{$url}.{$this->format}"; + } + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters); + $request->sign_request($this->sha1_method, $this->consumer, $this->token); + switch ($method) { + case 'GET': + return $this->http($request->to_url(), 'GET'); + default: + return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata()); + } + } + + /** + * Make an HTTP request + * + * @return API results + */ + function http($url, $method, $postfields = NULL) { + $this->http_info = array(); + $ci = curl_init(); + /* Curl settings */ + curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent); + curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout); + curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:')); + curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer); + curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader')); + curl_setopt($ci, CURLOPT_HEADER, FALSE); + + switch ($method) { + case 'POST': + curl_setopt($ci, CURLOPT_POST, TRUE); + if (!empty($postfields)) { + curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields); + } + break; + case 'DELETE': + curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE'); + if (!empty($postfields)) { + $url = "{$url}?{$postfields}"; + } + } + + curl_setopt($ci, CURLOPT_URL, $url); + $response = curl_exec($ci); + $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); + $this->url = $url; + curl_close ($ci); + return $response; + } + + /** + * Get the header info to store. + */ + function getHeader($ch, $header) { + $i = strpos($header, ':'); + if (!empty($i)) { + $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); + $value = trim(substr($header, $i + 2)); + $this->http_header[$key] = $value; + } + return strlen($header); + } +} + diff --git a/include/type.php b/include/type.php new file mode 100644 index 0000000..907c517 --- /dev/null +++ b/include/type.php @@ -0,0 +1,27 @@ +getRow( 'SELECT id, name, description FROM strip_t WHERE id=' . $id ); + $r->meta = $mtdb->getAll( 'SELECT meta as id from meta where type=' . $id); + return $r; +} + +function get_allTypes() { + global $mtdb; + return $mtdb->getRow( 'SELECT id, name, description, meta FROM strip_t' ); +} + +function get_allMetaTypes() { + global $mtdb; + return $mtdb->getAll("SELECT id, name FROM meta_t"); +} + +function _getMetaNameFromObject($obj) { + return $obj->name; +} + +?> \ No newline at end of file diff --git a/include/uploads.php b/include/uploads.php new file mode 100644 index 0000000..516add7 --- /dev/null +++ b/include/uploads.php @@ -0,0 +1,105 @@ +"; + + if(is_null($index)) { + if( '' == $_FILES[$field]['tmp_name'] ) return false; +# echo "Passed single test 2
"; + if( UPLOAD_ERR_NO_FILE == $_FILES[$field]['error'] ) return false; +# echo "Passed single test 3
"; + if( 0 == $_FILES[$field]['size'] ) return false; +# echo "Passed single test 4
"; + } else { + if( !is_array($_FILES[$field]['tmp_name']) ) return false; +# echo "Passed array text 2
"; + if( '' == $_FILES[$field]['tmp_name'][$index] ) return false; +# echo "Passed array test 3
"; + if( UPLOAD_ERR_NO_FILE == $_FILES[$field]['error'][$index] ) return false; +# echo "Passed array test 4
"; + if( 0 == $_FILES[$field]['size'][$index] ) return false; +# echo "Passed array test 5
"; + } + + return true; +} + + +function pre_upload_rant_image( $pathtofile ) { + if( !is_valid_upload() ) return array(); + $image_data = getimagesize( $pathtofile ); + $doing_upload = false; + $upload_imagetype = null; + $upload_error = false; + + if( false === $image_data ) { + $upload_error='Something wronky happened with that upload, getimagesize() returned false!
'; + } elseif( 300 > $image_data[0] ) { + $upload_error='Image too narrow, only '.$image_data[0].'px tall. Rant saved as draft. Try uploading image again.
'; + } elseif ( 245 > $image_data[1] ) { + $upload_error='Image too short, only '.$image_data[1].'px tall. Rant saved as draft. Try uploading image again.
'; + } elseif( false === $image_data[2] ) { + $upload_error='Unknown image type or extension. Upload refused. Rant saved as draft. Try uploading image again.
'; + } elseif( !is_uploaded_file( $pathtofile ) ) { + $upload_error="Something bad happened, that's not a valid file upload! Rant saved as draft. Try uploading image again.
"; + } else { + // Valid upload. It will be moved into place later. + $upload_imagetype = $image_data[2]; + $doing_upload = true; + } + return compact( "upload_error", "doing_upload", "upload_imagetype" ); +} + +function save_stock_rant_image( $source, $rant ) { + if( copy( sprintf( '%s/%s/%s', SITE_PATH_ABS,SITE_RANT,$source), + SITE_PATH_ABS .'/'.get_rantimage_filename($rant) ) ) { + $upload_info='Default rant image copied.
'; + } else { + $upload_error='There was an error copying the default rant image into place.
'; + } + return compact("upload_info","upload_error"); +} + +function save_upload_rant_image( $source, $rant ) { + $destination = SITE_PATH_ABS.'/'.get_rantimage_filename($rant); + $size = getimagesize($source); + + if(300 == $size[0] && 245 == $size[1]) { + if( move_uploaded_file($source, $destination) ) { + $upload_info='New rant image uploaded for rant '. $rant->id .'.
'; + } else { + $upload_error='Something went wrong while moving the uploaded image.
'; + } + } else { + if( crop_resize($source, $destination) ) { + $upload_info='New rant image uploaded and resized for rant '. $rant->id .'.
'; + } else { + $upload_error='Something went wrong while transforming the uploaded image.
'; + } + } + + return compact("upload_info","upload_error"); +} + +function save_upload_rant_attachment( $source, $rant ) +{ + global $mtdb; + + $image_data = getimagesize( $source ); + $mtdb->query( "INSERT INTO rant_attachment (rant, media) VALUES ($rant, $image_data[2])" ); + $rant_attachment_id = mysql_insert_id( $mtdb->link ); + + if( move_uploaded_file($source, SITE_PATH_ABS.'/'.get_rantattachment_filename($rant_attachment_id) ) ) { + $upload_info='New rant attachment uploaded for rant '. $rant .'.
'; + adminlog('Rant attachment uploaded', MTS_RANT, MTA_ADD); + } else { + $upload_error='Something went wrong while storing the attachment.
'; + } + + return compact("rant_attachment_id","upload_info","upload_error"); +} + +?> diff --git a/include/user.php b/include/user.php new file mode 100644 index 0000000..c8fbdb8 --- /dev/null +++ b/include/user.php @@ -0,0 +1,35 @@ +name; +} + +function get_userdatabyid( $id ) { + global $mtdb; + return $mtdb->getRow( 'SELECT id,name,email,nameplate,default_image,default_link FROM contributor WHERE id = ' . (int)$id ); +} + +function get_userdatabylogin( $username ) { + global $mtdb; + return $mtdb->getRow( 'SELECT id,name,email,nameplate,default_image,default_link FROM contributor WHERE name = "' . mysql_real_escape_string($username) . '"' ); +} + +function save_userdata( $user ) { + adminlog("Saved changes to user ".$user->id." (".$user->name.").", MTS_USER, MTA_UPDATE); + global $mtdb; + return $mtdb->query( sprintf( 'UPDATE contributor SET email="%s", nameplate="%s", default_image="%s", default_link="%s" WHERE id=%d', + mysql_real_escape_string($user->email), mysql_real_escape_string($user->nameplate), + mysql_real_escape_string($user->default_image), mysql_real_escape_string($user->default_link), $user->id) ); +} + +function change_password( $user ) { + adminlog("Changed password for user ".$user->id." (".$user->name.").", MTS_USER, MTA_UPDATE); + global $mtdb, $currentuser; + if( $currentuser->id === $user->id ) mt_setcookie($user->name, $user->password, false, ADMINURL, FALSE ); + return $mtdb->query( 'UPDATE contributor SET password=SHA1( "' . mysql_real_escape_string($user->password) . '" ) WHERE id = "' . mysql_real_escape_string($user->id) . '"' ); +} + +?> diff --git a/index.php b/index.php new file mode 100644 index 0000000..b48caa1 --- /dev/null +++ b/index.php @@ -0,0 +1,109 @@ + +Scratchpad
+
+ +Recent Strips
++getAll("SELECT distinct id, title, UNIX_TIMESTAMP(published) as date FROM strip WHERE published <= NOW() order by id DESC LIMIT 5"); + +foreach($strips as $k=>$v) { + printf( '
- %d: %s, %s ago
', $v->id, SITE_HOST . SITE_PATH, $v->id, htmlspecialchars($v->title), human_time_diff($v->date) ); +} +?> +
+ +Upcoming Strips
++getAll("SELECT distinct id, title, UNIX_TIMESTAMP(published) as date FROM strip WHERE published > NOW() order by id ASC LIMIT 5"); + +foreach($strips as $k=>$v) { + printf( '
- %d: %s, in %s
', $v->id, SITE_HOST . SITE_PATH . '/' . SITE_ADMIN, $v->id, htmlspecialchars($v->title), human_time_diff($v->date) ); +} +?> +
+ +Recent Published Rants
++getAll('SELECT distinct rant.id,UNIX_TIMESTAMP(rant.published) as date,rant.title,contributor.name from rant,contributor where rant.author=contributor.id AND rant.status=\'published\' ORDER BY rant.published DESC limit 5'); + +foreach($rants as $k=>$v) { + printf( '
- %d: %s by %s, %s ago
', $v->id, SITE_HOST . SITE_PATH, $v->id, htmlspecialchars($v->title), htmlspecialchars($v->name), human_time_diff($v->date) ); +} +?> +
+ +Recent Draft Rants
++getAll('SELECT distinct rant.id,UNIX_TIMESTAMP(rant.published) as date,rant.title,contributor.name from rant,contributor where rant.author=contributor.id AND rant.status=\'draft\' ORDER BY rant.published DESC limit 5'); + +foreach($rants as $k=>$v) { + printf( '
+ + diff --git a/login.php b/login.php new file mode 100644 index 0000000..b081fc6 --- /dev/null +++ b/login.php @@ -0,0 +1,97 @@ +Successfully logged you out.'; +?> + + + +- %d: %s by %s, %s ago
', $v->id, SITE_HOST . ADMIN_PATH, $v->id, htmlspecialchars($v->title), htmlspecialchars($v->name), human_time_diff($v->date) ); +} +?> +Megatokyo Admin › Login + + + + + + + + +\n"; + ?> + + + + + + + diff --git a/manage-comics.php b/manage-comics.php new file mode 100644 index 0000000..05596cb --- /dev/null +++ b/manage-comics.php @@ -0,0 +1,74 @@ + +Megatokyo Admin
+ $errorEdit Comic
+ +getOne("SELECT count(DISTINCT id) FROM strip") / $perpage ); +$strips = $mtdb->getAll("SELECT id, UNIX_TIMESTAMP(published) as published, type, media, title, book, page FROM strip GROUP BY id ORDER BY id DESC LIMIT $start,$perpage"); +$types_db = $mtdb->getAll("SELECT id,description FROM strip_t"); + +$type = array(); +foreach( $types_db as $k ) $type[$k->id]=$k->description; + + +pagination( $page, $total ); + +?> + ++ +
+ + + + diff --git a/manage-metatypes.php b/manage-metatypes.php new file mode 100644 index 0000000..ea01b73 --- /dev/null +++ b/manage-metatypes.php @@ -0,0 +1,106 @@ +query( 'DELETE FROM meta_t WHERE id=' . (int)$_GET['delete'] ) ) + { + adminlog("Error on deleting metatype ".(int)$_GET['delete'], MTS_TYPE_META, MTA_DELETE, E_WARNING); + mtdie("Error on update: ". htmlentities(mysql_error())); + } + $info.='+ + + + + +Strip # +Title +Published On +Type +Book Page # ++ + + > + + + +id; ?> +title; ?> +published)); ?> +type]; ?> +book; ?> - page; ?> +View +Edit +Delete +Deleted metatype successfully.
'; + adminlog("Metatype ".(int)$_GET['delete']." deleted.", MTS_TYPE_META, MTA_DELETE); +} + +if( isset($_POST['action']) && $_POST['action'] == 'new_meta' ) { + check_nonce('new-metatype'); + + $name = trim($_POST['name']); + + if( check_type_name( $name ) ) { + if(! $mtdb->query( 'INSERT INTO meta_t(name) VALUES("'. mysql_real_escape_string($name) . '")' ) ) + { + adminlog("Error on inserting metatype ".(int)$_GET['delete'], MTS_TYPE_META, MTA_INSERT, E_WARNING); + mtdie("Error on insertion: ". htmlentities(mysql_error())); + } + } + $info.='
New metatype created successfully.
'; + adminlog("Metatype ".$name." added.", MTS_TYPE_META, MTA_ADD); +} + +if( isset($_POST['action']) && $_POST['action'] == 'edit_meta' ) { + check_nonce('save-metatype-'.(int)$_POST['type_id']); + + $name = trim($_POST['name']); + + if( check_type_name( $name ) ) { + if(! $mtdb->query( 'UPDATE meta_t SET name = "' . mysql_real_escape_string($name) . '" WHERE id=' . (int)$_POST['type_id']) ) + { + adminlog("Error updating metatype ".(int)$_GET['delete'], MTS_TYPE_META, MTA_UPDATE, E_WARNING); + mtdie("Error on update: ". htmlentities(mysql_error())); + } + } + $info.='
Changes to metatype saved successfully.
'; + adminlog("Metatype ".$name." updated.", MTS_TYPE_META, MTA_UPDATE); +} + +//get all metatypes +$metas = $mtdb->getAll("SELECT id, name FROM meta_t"); + +adminhead('Metatypes'); +adminmenu(); +?> +
Metatype Management
+Make changes to the metatypes which organize the types.
+ ++ +
+ + + + \ No newline at end of file diff --git a/manage-pages.php b/manage-pages.php new file mode 100644 index 0000000..d3ecebf --- /dev/null +++ b/manage-pages.php @@ -0,0 +1,67 @@ + ++ + + + + +Metatype # +Name ++ + > + + + +id; ?> +name); ?> +Edit +Delete +Manage Pages
+ +getOne("SELECT count(DISTINCT url_name) FROM static_page") / $perpage ); +$pages = $mtdb->getAll("SELECT url_name, pubdate, status, title, body FROM static_page ORDER BY url_name ASC LIMIT $start,$perpage"); + +pagination( $page, $total ); + +?> + ++ +
+ + diff --git a/manage-rants.php b/manage-rants.php new file mode 100644 index 0000000..80edd37 --- /dev/null +++ b/manage-rants.php @@ -0,0 +1,69 @@ + ++ + + + + +Page Name +Title +Status +Published On +Excerpt ++ + + > + + + +url_name ?> +title; ?> +status; ?> +pubdate; ?> +body,185); ?> +url_name . '">View'; ?> +Edit +Delete +Manage Rants
+ +getOne("SELECT count(DISTINCT id) FROM rant") / $perpage ); +$rants = $mtdb->getAll("SELECT r.id,UNIX_TIMESTAMP(r.published) AS published,c.name,r.title,r.body, r.status FROM rant r,contributor c WHERE c.id=r.author GROUP BY id ORDER BY id DESC LIMIT $start,$perpage"); + +pagination( $page, $total ); + +?> + ++ +
+ + diff --git a/manage-statusbox.php b/manage-statusbox.php new file mode 100644 index 0000000..c943f2e --- /dev/null +++ b/manage-statusbox.php @@ -0,0 +1,167 @@ +Percentage complete must be numeric.'; + return; + } + if( $percent < 0 || $percent > 100 ) { + $error = '+ + + + + +Rant # +Author +Title +Status +Published On +Excerpt ++ + + > + + + +id; ?> +name; ?> +title; ?> +status; ?> +published)); ?> +body,185); ?> +id . '">View'; ?> +Edit +Delete +Supply percentages between 0 and 100 (inclusive), please.
'; + return; + } + + // ETA must be sane + if( ($eta < $now && $percent < 100) || ($etaSupply estimated time in the future, please. Current time is ' . date("F j, Y, g:i a", $now) . ''; + return; + } + + $mtdb->query( 'INSERT INTO status (published,eta,percentage,text) VALUES( NOW(), FROM_UNIXTIME(' . (int)$eta . '), '. (int)$percent . ', "' . mysql_real_escape_string($text) . '")' ); + + $_POST['update_percentage']=$_POST['update_eta']=$_POST['update_text']=''; + $info = ' Statusbox updated successfully.
'; +} + +if( isset($_POST['action']) && $_POST['action'] == 'create-update' ) + handle_update_form(); + + + + +adminhead('Status Box'); +adminmenu('manage-statusbox.php'); + + +/* Simple Presets, Select things said before */ + + +$presets = $mtdb->getAll('SELECT COUNT(*) as c, percentage, text, CONCAT( percentage, "% - ", text ) as p FROM status GROUP BY p HAVING c>1 ORDER BY c DESC'); + +?> + + + + + + ++ ++ + diff --git a/users.php b/users.php new file mode 100644 index 0000000..e9f2a32 --- /dev/null +++ b/users.php @@ -0,0 +1,70 @@ + +Status Box History
+Status updates that have gone before.
+ +getAll("SELECT published,eta,percentage,text FROM status ORDER BY published DESC limit 5"); + +?> + ++ +
+ + + diff --git a/manage-twitter-presets.php b/manage-twitter-presets.php new file mode 100644 index 0000000..2177400 --- /dev/null +++ b/manage-twitter-presets.php @@ -0,0 +1,84 @@ +query( sprintf('INSERT INTO twitter_status (position, message) VALUES (%d, "%s")', $position, mysql_real_escape_string($msg)) ); + adminlog("Added new preset: $msg", MTS_TWITTER, MTA_ADD); + } elseif(empty($msg)) { + // Delete an existing preset + $mtdb->query( "DELETE FROM twitter_status WHERE id = $id" ); + adminlog("Removed preset: $id", MTS_TWITTER, MTA_ADD); + } else { + // Modify an existing preset + $mtdb->query( sprintf('UPDATE twitter_status SET position = %d, message = "%s" WHERE id = %d', $position, mysql_real_escape_string($msg), $id) ); + } + } +} + +$statuses = $mtdb->getAll('SELECT id, position, message FROM twitter_status ORDER BY position, id'); + +adminhead('Manage Twitter Presets'); +adminmenu(); + +?> + ++ + + + + +Published +ETA +Percentage +Text +> + + + +published; ?> +eta; ?> +percentage; ?> +text); ?> +Manage Twitter Presets
+To add a new preset, enter it into the empty box below.
+To delete a preset, remove all text from its message box.
+ + + + + + diff --git a/manage-twitter-users.php b/manage-twitter-users.php new file mode 100644 index 0000000..8e14058 --- /dev/null +++ b/manage-twitter-users.php @@ -0,0 +1,156 @@ +query( sprintf('INSERT INTO twitter_user(username) VALUES("%s")', mysql_real_escape_string( md5( microtime() )) ) ) ) { + adminlog("Error on insertion of new twitter user.", MTS_TWITTER, MTA_INSERT, E_WARNING); + mtdie("Error on insertion of new twitter user: ". htmlentities(mysql_error()), 'SQL Error'); + } else { + //$name = sanitize_username($_POST['name']); + + $id = mysql_insert_id(); + + $connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET); + $request_token = $connection->getRequestToken(OAUTH_CALLBACK . "&id=$id"); + + if ($connection->http_code !== 200 ) { + adminlog("Twitter getRequestToken failed. HTTP code: $connection->http_code", MTS_TWITTER, MTA_MODIFY); + mtdie("Could not connect to twitter.com."); + } + + echo $id . '
'; + + setOAuthTokens( $id, $request_token['oauth_token'], $request_token['oauth_token_secret'], md5(microtime()) ); + + adminlog("New twitter user created successfully.", MTS_TWITTER, MTA_ADD); + + $url = $connection->getAuthorizeURL($request_token['oauth_token']); + //echo $url; + _redirect($url); + exit(); + + } +} + +if( isset($_REQUEST['action']) && $_REQUEST['action'] == 'twittercallback' && isset($_REQUEST['id'])) { + # twitter userID = ID + $id = (int)$_REQUEST['id']; + $row = $mtdb->getRow( sprintf('SELECT id, username, oauth_token, oauth_token_secret, oauth_access_token FROM twitter_user WHERE id=%d LIMIT 1', $id)); + + # Compare token in database with token from twitter. If they differ, bail. + if( $row->oauth_token != $_REQUEST['oauth_token'] ) { + # token is old, drop from database + if(!$mtdb->query("DELETE FROM twitter_user WHERE id = '$id'") ) { + adminlog('Error deleting temporary twitter user ' . $id, MTS_TWITTER, MTA_DELETE, E_ERROR); + mtdie('Error deleting temporary twitter user.', 'SQL Error'); + } + $error.='OAuth Token are Old
'; + } else { + # token is good, save the new Access Token to the database + $connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $row->oauth_token, $row->oauth_token_secret); + + $access_token = $connection->getAccessToken($_REQUEST['oauth_verifier']); + + if (200 == $connection->http_code) { + # successful + $info.='Successfully obtained OAuth Access Token.
'; + $content = $connection->get('account/verify_credentials'); + $username = $content->screen_name; + + setOAuthTokens($id, $access_token['oauth_token'], $access_token['oauth_token_secret'], $username); + adminlog("Successfully received OAuth Access Tokens for twitter user.", MTS_TWITTER, MTA_MODIFY, E_WARNING); + + //print_r($content); + + } else { + # fail + if( !$mtdb->query("DELETE FROM twitter_user WHERE id = '$id'") ) { + adminlog('Error deleting specified twitter user ' . $id, MTS_TWITTER, MTA_DELETE, E_ERROR); + mtdie('Error deleting the specified twitter user.', 'SQL Error'); + } + $info.='Failed to get OAuth Access Token for ' . $username . '.
'; + adminlog("Failed to get OAuth Access Tokens for twitter user.", MTS_TWITTER, MTA_MODIFY, E_ERROR); + } + + } +} + +$twitter_users = $mtdb->getAll('SELECT id, username, oauth_token, oauth_token_secret, oauth_access_token FROM twitter_user ORDER BY username'); + +adminhead('Manage Twitter Users'); +adminmenu(); + +?> + +Manage Twitter Users
+Make changes to the twitter accounts which we can post to.
+ + + ++ +
+ + + + + diff --git a/manage-types.php b/manage-types.php new file mode 100644 index 0000000..cf493fa --- /dev/null +++ b/manage-types.php @@ -0,0 +1,179 @@ +query( 'DELETE FROM strip_t WHERE id=' . (int)$_GET['delete'] ) ) + { + adminlog("Error deleting type ".(int)$_GET['delete'], MTS_TYPE, MTA_DELETE, E_WARNING); + mtdie("Error on deletion of existing type: " . htmlentities(mysql_error()), 'SQL Error'); + } + if(! $mtdb->query( 'DELETE FROM meta WHERE type=' . (int)$_GET['delete'] ) ) + { + adminlog("Error on deletion of type ".(int)$_GET['delete']."'s metadata.", MTS_TYPE, MTA_DELETE, E_WARNING); + mtdie("Error on deletion of existing type's metadata: " . htmlentities(mysql_error()), 'SQL Error'); + } + $info.='+ + + + + +ID # +Twitter.com Username +oauth_token +oauth_token_secret +Authorized ++ > + + + +id; ?> +username); ?> +oauth_token); ?> +oauth_token_secret); ?> +oauth_token, $s->oauth_token_secret); + $content = $connection->get('account/verify_credentials'); + if( isset($content->profile_image_url)) { + echo ' +'; + } else { + echo 'Not Authorized'; + } + ?>
Delete +Deleted type successfully.
'; + adminlog("Deleted type ".(int)$_GET['delete'], MTS_TYPE, MTA_DELETE); +} + +if( isset($_POST['action']) && $_POST['action'] == 'new_type' ) { + check_nonce('new-type'); + + $name = trim($_POST['name']); + $desc = trim($_POST['description']); + + if( check_type_name($name) ) { + if(! $mtdb->query( sprintf( 'INSERT INTO strip_t(name, description) VALUES("%s", "%s")', mysql_real_escape_string($name), mysql_real_escape_string($desc)) ) ) + { + adminlog("Error on insertion of new type.", MTS_TYPE, MTA_INSERT, E_WARNING); + mtdie("Error on insertion of new type: ". htmlentities(mysql_error()), 'SQL Error'); + } + } + $info.='
New type created successfully.
'; + adminlog("Type '".$name."' created successfully.", MTS_TYPE, MTA_ADD); +} + +if( isset($_POST['action']) && $_POST['action'] == 'edit_type' ) { + + $id = (int)$_POST['type_id']; + check_nonce("save-type-$id"); + + $name = trim($_POST['name']); + $desc = trim($_POST['description']); + + $meta = $_POST['meta']; + + $m_delete = $mtdb->getAll("SELECT meta FROM meta WHERE type = $id"); + + $m_insert = array(); + + // Key listed in both Insert and Delete lists, so remove from both == Do Nothing + foreach( $m_delete as $k=>$v ) { + if( array_key_exists( $v->meta, $meta ) ) { + unset($m_delete[$k]); + unset($meta[$v->meta]); + } else { + $m_delete[$k] = 'meta=' . (int)$v->meta; + } + } + + // Key listed only in Insert list, make proper format + foreach( $meta as $k=>$v ) { + $m_insert[] = "($id," . (int)$k . ')'; + } + + if( check_type_name( $name ) ) { + if( !$mtdb->query( sprintf( 'UPDATE strip_t SET name = "%s", description = "%s" WHERE id = %s', mysql_real_escape_string($name), mysql_real_escape_string($desc), $id)) ) + { + adminlog("Error on updating type ".$id, MTS_TYPE, MTA_UPDATE, E_WARNING); + mtdie("Error on update of existing type: ". htmlentities(mysql_error()), 'SQL Error'); + } + + $sql_insert = "INSERT INTO meta (type,meta) VALUES " . implode(',',$m_insert); + $sql_delete = "DELETE FROM meta WHERE type=$id AND ( " . implode(' OR ',$m_delete) . ' )'; + + $mtdb->query('START TRANSACTION'); + + if( count($m_insert) ) + if(! $mtdb->query( $sql_insert ) ) + { + adminlog("Error inserting new metatype association data for type ".$id, MTS_TYPE, MTA_INSERT, E_WARNING); + mtdie("There was an error inserting new metatype association data. Transaction aborted. $sql_insert"); + } + if( count($m_delete) ) + if(! $mtdb->query( $sql_delete ) ) + { + adminlog("Error deleting old metatype association data for type ".$id, MTS_TYPE, MTA_REMOVE, E_WARNING); + mtdie("There was an error deleting old metatype data. Transaction aborted. $sql_delete"); + } + + $mtdb->query('COMMIT'); + + } else { + $error.='
Invalid type name!
'; + } + $info.='Changes to type saved successfully.
'; + adminlog("Type ".$id." updated.", MTS_TYPE, MTA_UPDATE); +} + +//display all types +$types = $mtdb->getAll("SELECT strip_t.id AS id, strip_t.name AS name, strip_t.description AS description, COUNT(strip.id) AS strips FROM strip_t LEFT JOIN strip ON strip.type = strip_t.id GROUP BY strip_t.id"); + +adminhead('Types'); +adminmenu(); + +?> + +
Type Management
+Make changes to the types which categorize the comics.
+ ++ +
+ + + + + diff --git a/post-comic.php b/post-comic.php new file mode 100644 index 0000000..1af94a0 --- /dev/null +++ b/post-comic.php @@ -0,0 +1,103 @@ +getOne( 'SELECT type FROM strip ORDER BY id DESC limit 1' ); +$last_strip_id = $mtdb->getOne( 'SELECT MAX(id) FROM strip' ); + +adminhead('Post Comic'); +adminmenu('post-comic.php'); + +$tomorrow = strtotime('tomorrow'); +$post_date = time(); + +if(isset($_REQUEST['next']) && $_REQUEST['next'] == "yes") +{ + //in general, this is the case that is desired + // the next monday, wednesday, or friday that isn't today + $post_date = min( strtotime("next Monday +1 hour", $tomorrow), strtotime("next Wednesday +1 hour", $tomorrow), strtotime("next Friday +1 hour", $tomorrow) ); + + //however, if it is monday, wednesday, or friday AND before 1 AM + // then we want to post at 1 AM on this day + $today = date("l"); + if(($today == "Monday" || $today == "Wednesday" || $today == "Friday") && date("G") == 0) + { + $post_date = "today +1 hour"; + } +} + +?> + ++ + + + + getAll("SELECT meta_t.name AS name FROM strip_t + JOIN meta ON meta.type = strip_t.id JOIN meta_t ON meta.meta = meta_t.id + WHERE strip_t.id = $s->id"); + + $meta = implode(', ', array_map('_getMetaNameFromObject', $metas) ); + + ?> +Type # +Name +Description +Strips +Metatypes ++ + > + + + +id; ?> +name); ?> +description); ?> +strips ?> ++ Edit +Delete +Post New Comic
+ + + diff --git a/post-page.php b/post-page.php new file mode 100644 index 0000000..8b3d66c --- /dev/null +++ b/post-page.php @@ -0,0 +1,104 @@ + + + +Create New Page
+ + + + diff --git a/post-rant.php b/post-rant.php new file mode 100644 index 0000000..3c1c4a8 --- /dev/null +++ b/post-rant.php @@ -0,0 +1,194 @@ +getOne( 'SELECT extension FROM media_t WHERE id=' . (int)$currentuser->imagetype ); + +?> + + + + +Create New Rant
+ + + + diff --git a/post-scratchpad.php b/post-scratchpad.php new file mode 100644 index 0000000..e340b89 --- /dev/null +++ b/post-scratchpad.php @@ -0,0 +1,14 @@ +query( sprintf( 'INSERT INTO scratchpad (contributor, message) VALUES (%d, "%s")', (int)$currentuser->id, mysql_real_escape_string($_REQUEST['message'])) ); + +adminlog("User posted to scratchpad.", MTS_SCRATCH, MTA_INSERT); +_redirect( ADMIN_PATH . '/index.php' ); + +?> diff --git a/post-twitter.php b/post-twitter.php new file mode 100644 index 0000000..8fd01fe --- /dev/null +++ b/post-twitter.php @@ -0,0 +1,148 @@ +getOne( 'SELECT MAX(id) FROM strip' ); + $next_strip_id += 1; + $postmessage = str_replace("#nextcomic", $next_strip_id, $postmessage); + + if('' == $postmessage) _redirect( ADMIN_PATH . '/post-twitter.php?tweet=missing'); + $username = sanitize_username($_REQUEST['twitter_user']); + $postasuser = $mtdb->getOne( sprintf('SELECT username FROM twitter_user WHERE username="%s"', mysql_real_escape_string($username))); + + if( in_array('twitter', $_REQUEST['service']) ) + $rc = twitterpost( numeric_entities(utfentities($postmessage)), $postasuser ); + if( in_array('rss', $_REQUEST['service']) ) + $rc = rsspost( numeric_entities(utfentities($postmessage)), SITE_HOST.SITE_PATH ); + + if($rc) _redirect( ADMIN_PATH . '/post-twitter.php?tweet=success'); + _redirect( ADMIN_PATH . '/post-twitter.php?tweet=fail'); + + // Shall not pass, all routes lead to redirect. +} + +if( isset($_REQUEST['tweet']) && 'success' == $_REQUEST['tweet'] ) + $info.='Status successfully posted to Twitter.'; + +if( isset($_REQUEST['tweet']) && 'fail' == $_REQUEST['tweet'] ) + $info.='Status could not be posted to Twitter. Is it up?'; + +if( isset($_REQUEST['tweet']) && 'missing' == $_REQUEST['tweet'] ) + $error.='Oops~ Looks like you forgot to enter a message.'; + + +$statuses = $mtdb->getAll('SELECT id, position, message FROM twitter_status ORDER BY position, id'); +$twitter_users = $mtdb->getAll('SELECT id, username, oauth_token, oauth_token_secret, oauth_access_token FROM twitter_user ORDER BY username'); + +adminhead('Update Twitter'); +adminmenu(); + +?> + +Update Twitter
+ + + + + + */ ?> + +
+ ++ + diff --git a/rss-adminlog.php b/rss-adminlog.php new file mode 100644 index 0000000..6e720e3 --- /dev/null +++ b/rss-adminlog.php @@ -0,0 +1,36 @@ +getAll("SELECT UNIX_TIMESTAMP(l.logdate) AS logdate, c.name AS cname, c.email AS cmail, s.name AS section, action, level, message FROM admin_log l JOIN admin_section s ON l.section = s.id LEFT JOIN contributor c ON l.contributor = c.id ORDER BY l.logdate DESC LIMIT $count"); + +header("Content-Type: application/rss+xml;charset=utf-8"); + +echo "\n"; +?> +
+$v) { ?> + + + + ++ diff --git a/rss-scratchpad.php b/rss-scratchpad.php new file mode 100644 index 0000000..d932663 --- /dev/null +++ b/rss-scratchpad.php @@ -0,0 +1,33 @@ +getAll("SELECT UNIX_TIMESTAMP(s.published) AS pubdate, c.name AS cname, c.email AS cmail, message FROM scratchpad s JOIN contributor c ON s.contributor = c.id ORDER BY s.published DESC LIMIT $count"); + +header("Content-Type: application/rss+xml;charset=utf-8"); + +echo "\n"; +?> ++ +Megatokyo Admin Log + http://www.megatokyo.com/admin +Listing of administrative events: errors, warnings, and notifications. +en-us + + $v) { ?> +- +
+ +message, ENT_COMPAT, 'UTF-8') ?> +cmail, $v->cname) ?> +logdate ) ?> +logdate) ?> + +level ?> +section ?> +action ?> +message, ENT_COMPAT, 'UTF-8') ?> ++ diff --git a/rss-striplog.php b/rss-striplog.php new file mode 100644 index 0000000..6d78c2f --- /dev/null +++ b/rss-striplog.php @@ -0,0 +1,31 @@ +getAll("SELECT UNIX_TIMESTAMP(l.logdate) AS logdate, s.name AS section, action, message FROM admin_log l JOIN admin_section s ON l.section = s.id WHERE s.name = 'strip' ORDER BY l.logdate DESC LIMIT $count"); + +header("Content-Type: application/rss+xml;charset=utf-8"); + +echo "\n"; +?> ++ +Megatokyo Admin Scratchpad + http://www.megatokyo.com/admin +Listing of administrative events: errors, warnings, and notifications. +en-us + + $v) { ?> +- +
+ +cname, ': ', htmlentities($v->message, ENT_COMPAT, 'UTF-8') ?> +cmail, $v->cname) ?> +pubdate ) ?> +pubdate) ?> + +cname, ': ', htmlentities($v->message, ENT_COMPAT, 'UTF-8') ?> ++ diff --git a/swap-comics.php b/swap-comics.php new file mode 100644 index 0000000..06e0496 --- /dev/null +++ b/swap-comics.php @@ -0,0 +1,58 @@ +Strips $a and $b swapped successfully.'; + adminlog("Strips $a and $b have been swapped.", MTS_STRIP, MTA_MODIFY); +} + + + +adminhead('Swap Comics'); +adminmenu('swap-comics.php'); + +?> + ++ +Megatokyo Admin Log + http://www.megatokyo.com/admin +Listing of administrative events: errors, warnings, and notifications. +en-us + + $v) { ?> +- +
+ +message, ENT_COMPAT, 'UTF-8') ?> +cmail, $v->cname) ?> +logdate ) ?> +logdate) ?> +action ?> +message, ENT_COMPAT, 'UTF-8') ?> +Swap Comics
+ + + + diff --git a/twitter-scheduled.php b/twitter-scheduled.php new file mode 100644 index 0000000..5faa8c2 --- /dev/null +++ b/twitter-scheduled.php @@ -0,0 +1,43 @@ +getAll("SELECT username, password, text, status, twitter_post.id AS id + FROM twitter_post JOIN twitter_user + ON twitter_post.user = twitter_user.id + WHERE twitter_post.status = 'scheduled' + AND time >= NOW() + AND time < TIMESTAMPADD(" . RUN_INTERVAL . ", NOW()) +"); + +// Check if we actually have any tweets. If not, bail. +if(count($tweets) === 0) +{ + exit(0); +} + +// There are tweets to post. Let's get to work. + +foreach($tweets as $t) +{ + // Lock the tweet + $mtdb->query("UPDATE twitter_post SET status = 'locked' WHERE id = ".(int)$t->id." AND status = 'scheduled'", false); + + if(twitterpost($t->text, $t->username, $t->password)) + { + // It worked! + adminlog("Scheduled tweet posted for user ".$t->username, MTS_TWITTER, MTA_ADD); + $t->status = 'success'; + } + else + { + // Well, shit. Something went wrong. Log it. + adminlog("Error $ret_code posting scheduled tweet ".$t->id . ' with return value ' . $ret, MTS_TWITTER, MTA_ADD); + $t->status = 'error'; + } + + // Unlock tweet, update db. + $mtdb->query("UPDATE twitter_post SET status = '".mysql_real_escape_string($t->status)."' WHERE status = 'locked' AND id = ".(int)$t->id, false); +} diff --git a/user-edit.php b/user-edit.php new file mode 100644 index 0000000..6f87eb4 --- /dev/null +++ b/user-edit.php @@ -0,0 +1,179 @@ +query( 'INSERT INTO contributor (name, default_image) VALUES ("' . mysql_real_escape_string($username) . '", "'.$username.'.png")' ); + $user = get_userdatabylogin( $username ); + $userid = $user->id; + $info.='User Account Created
'; + adminlog("User '".$username."' created.", MTS_USER, MTA_ADD); + $user_old = $user; + } else { + $userid = (int) $_POST['edit']; + $user_old = $user = get_userdatabyid( $userid ); + } + + $user->nameplate = $_POST['nickname']; + $user->default_image = $user_old->default_image; + $user->default_link = $_POST['rant-link']; + $user->email = $_POST['email']; + + if( !empty($_POST['password_new1']) && !empty($_POST['password_new2']) ) { + + if( $_POST['password_new1'] !== $_POST['password_new2'] ) { + $error.='New passwords do not match.
'; + } else { + /* password change */ + if( ! $mtdb->getOne( 'SELECT id FROM contributor WHERE id = "' . (int)$user->id . '" AND (password = SHA1("' . mysql_real_escape_string($_POST['password_old']) . '") OR password = "")' )) { + $error.='Specified password is incorrect.
'; + } else { + /* Password match */ + $user->password = $_POST['password_new1']; + change_password( $user ); + $info.='Password successfully changed.
'; + } + } + } + + function handle_upload( &$user ) { + global $info,$error; + + if( !$_FILES['rant_image'] ) return; + + if( '' == $_FILES['rant_image']['name'] ) return; + if( UPLOAD_ERR_NO_FILE == $_FILES['rant_image']['error'] ) return; + if( 0 == $_FILES['rant_image']['size'] ) return; + + $info.='Tried to upload an image.
'; + // Uploading new rant image + $imagedata = getimagesize($_FILES['rant_image']['tmp_name']); + if( 300 !== $imagedata[0] ) { + $error.='Image wrong width: '.$imagedata[0].'
'; + return; + } + if( 245 !== $imagedata[1]) { + $error.='Image wrong height: '.$imagedata[1].'
'; + return; + } + + switch( $_FILES['rant_image']['type'] ) { + case 'image/jpeg': + case 'image/jpg': $ext = 'jpg'; break; + case 'image/gif': $ext = 'gif'; break; + case 'image/png': $ext = 'png'; break; + case 'image/bmp': $ext = 'bmp'; break; + case 'image/tiff': $ext = 'tiff'; break; + default: + $error.='Unknown image extension. Upload refused.
'; + return; + } + + $destination_path = $user->name.'.'.$ext; + if( !is_uploaded_file( $_FILES['rant_image']['tmp_name'] )) { + $error.='Something went wrong while retrieving the uploaded image.
'; + return; + } + if( move_uploaded_file($_FILES['rant_image']['tmp_name'], RANTIMG.$destination_path) ) { // TODO: SITE_PATH_ABS .'/'. SITE_RANT ? + // great + $user->default_image = $destination_path; + $info.='New rant image uploaded.
'; + } else { + $error.='Something went wrong while storing the uploaded image.
'; + adminlog("File system error while uploading rant image.", MTS_USER, MTA_MODIFY, E_WARNING); + } + } + + handle_upload( $user ); + save_userdata( $user ); + $info.='Changes to user profile information were saved successfully.
'; + adminlog("Profile updated for user ".$user->name.".", MTS_USER, MTA_UPDATE); +} else { + $userid = (int) $_GET['edit']; + $user = get_userdatabyid( $userid ); +} + +if( !$user ) $error.='The specified user does not exist.
'; + +adminhead('Edit User Profile'); +adminmenu('users.php'); + +if( $user ) { + +?> + +User Administration
+Make changes to accounts for contributers to the website.
+ +getAll("SELECT id,name,email,nameplate FROM contributor"); + +?> + ++ +
+ + + + + diff --git a/view-adminlog.php b/view-adminlog.php new file mode 100644 index 0000000..2edebd7 --- /dev/null +++ b/view-adminlog.php @@ -0,0 +1,65 @@ + ++ + + + + +User # +Username +Nickname ++ > + + + +id; ?> +name; ?> +nameplate; ?> +email; ?> +Edit +Admin Log
+ +getOne("SELECT COUNT(*) FROM admin_log") / $perpage ); +$entries = $mtdb->getAll("SELECT UNIX_TIMESTAMP(l.logdate) AS logstamp, l.logdate AS logdate, c.name AS cname, c.email AS cmail, s.name AS section, action, level, message FROM admin_log l JOIN admin_section s ON l.section = s.id LEFT JOIN contributor c ON l.contributor = c.id ORDER BY l.logdate DESC LIMIT $start,$perpage"); + +pagination( $page, $total ); + +?> + ++ +
+ + + + diff --git a/wp-admin.css b/wp-admin.css new file mode 100644 index 0000000..2ed65ba --- /dev/null +++ b/wp-admin.css @@ -0,0 +1,1341 @@ +* html #poststuff { + height: 100%; /* kill peekaboo bug in IE */ +} + +/* This is the Holly Hack \*/ +* html .wrap { height: 1% } +/* For Win IE's eyes only */ + +body { + border: none; +} +a { + border-bottom: 1px solid #69c; + color: #00019b; + text-decoration: none; +} + +a.delete:hover { + background: #c00; + color: #fff; +} + +#devnews h4 { + font-family: Georgia, "Times New Roman", Times, serif; + font-size: 18px; + font-weight: normal; +} + +#planetnews ul { + list-style: none; + margin: 0; + padding: 0; +} + +#planetnews li { + width: 17%; + margin: 1%; + float: left; +} + +#planetnews li a { + display: block; + padding: .5em; + background: #ddd; + height: 6em; + overflow: hidden; +} + +#planetnews cite { + font-size: 11px; +} + +#planetnews li .post { + font-family: Georgia, "Times New Roman", Times, serif; + font-size: 18px; + display: block; + height: 60px; + overflow: hidden; +} + +#planetnews .hidden { + display: none; +} + +.readmore { + clear: both; + text-align: right; + margin-right: 5em; +} + +.widefat { + width: 100%; +} + +.widefat td, .widefat th { + padding: 5px 6px; +} + +.widefat th { + text-align: left; +} + +.plugins p { + margin: 4px; + padding: 0; +} + +.plugins .name { + font-size: 16px; +} + +.import-system { + font-size: 16px; +} + +thead, .thead { + background: #dfdfdf +} + +#import-upload-form { + margin: auto; + background: #eee; + padding: 1em; +} + +a.edit, a.delete, a.edit:hover, a.delete:hover { + border-bottom: none; + display: block; + padding: 5px 0; + text-align: center; +} + +a.edit:hover { + background: #ccc; + color: #036; +} + +a:visited { + color: #006; +} + +a:hover { +/* border-bottom: 1px solid #3a75ae;*/ + color: #069; +} + +body { + background: #f9fcfe; + color: #000; + margin: 0; + padding: 0; +} + +body, td { + font: 13px "Lucida Grande", "Lucida Sans Unicode", Tahoma, Verdana; +} + +fieldset { + border: none; + padding: 3px; +} + +fieldset label.selectit { + display: block; + font-size: 11px; + padding: 0 2px; +} + +fieldset label.selectit:hover { + background: #e9e9e9; +} + +fieldset legend { + padding: .1em .3em; +} + +fieldset.options { + padding: 1em; +} + +fieldset.options legend { + font-size: 1.5em; + font-weight: bold; + font-family: Georgia, "Times New Roman", Times, serif; +} + +form, label input { + margin: 0; + padding: 0; +} + +h2 { + border-bottom: .5em solid #e5f3ff; + color: #333; + font: normal 32px/5px serif; + margin: 5px 10px; +} + +img, #footer a { + border: 0; +} + +input:focus, textarea:focus, label:focus { + background: #fff; + border: 1px solid #686868; +} + +label { + cursor: pointer; +} + +li, dd { + margin-bottom: 6px; +} + +p, li, dl, dd, dt { + line-height: 140%; +} + +textarea, input, select { + background: #f4f4f4; + border: 1px solid #b2b2b2; + color: #000; + font: 13px Verdana, Arial, Helvetica, sans-serif; + margin: 1px; + padding: 3px; +} + +#uploading { + border-style: none; + padding: 0px; + margin-bottom: 16px; + height: 18em; + width: 100%; +} + +form#upload th { + text-align: right; +} + +form#upload #post_content, form#upload #post_title { + width: 250px; +} + +form#upload #post_content { + height: 50px; +} + +.attpreview { + width: 1px; /* hug */ + text-align: center; +} + +.alignleft { + float: left +} + +.alignright { + float: right; +} + +.alternate { + background: #d1d1d1; +} + +.anchors { + margin: 10px 20px 10px 20px; +} + +.available-theme { + width: 30%; + margin: 0 1em; + float: left; + text-align: center; + height: 28em; + overflow: hidden; +} + +.available-theme a.screenshot { + width: 250px; + height: 200px; + display: block; + margin: auto; + background: #f1f1f1; + border: 1px solid #ccc; + margin-bottom: 10px; + overflow: hidden; +} + +.available-theme a.screenshot:hover { +/* border: 1px solid #666;*/ +} + +.available-theme img { + width: 100%; +} + +.checkbox { + background: #fff; + border: none; + margin: 0; + padding: 0; +} + +.code { + font-family: "Courier New", Courier, monospace; +} + +.commentlist li { + border-bottom: 1px solid #ccc; + padding: 1em 1em .2em; + margin: 0; +} + +.commentlist p { + padding: 0; + margin: 0 0 .8em; +} + +.clear { + clear: both; + height: 2px; +} + +.hidden { + display: none; +} + +.navigation { + display: block; + text-align: center; + margin-top: 10px; + margin-bottom: 30px; +} + +.post-categories { + display: inline; + margin: 0; + padding: 0; +} + +.post-categories li, #ed_toolbar { + display: inline; +} + +.quicktags, .search { + background: #ccc; + color: #000; + font: 12px Georgia, "Times New Roman", Times, serif; +} + +.submit input, .submit input:focus, .button, .button:focus { + background: url( images/fade-butt.png ); + border: 3px double #999; + border-left-color: #ccc; + border-top-color: #ccc; + color: #333; + padding: 0.25em; +} + +.submit input:active, .button:active { + background: #f4f4f4; + border: 3px double #ccc; + border-left-color: #999; + border-top-color: #999; +} + +.button, .button:focus { + padding: 0.15em; +} + +* html .button { + padding: 0; +} + +.submit, .editform th, #postcustomsubmit { + text-align: right; +} + +.optiontable { + width: 100%; +} + +.optiontable td, .optiontable th { + padding: .5em; +} + +.optiontable th { + width: 33%; + text-align: right; + font-size: 1.3em; + font-weight: normal; +} + +.unapproved { + color: #888; +} + +.unapproved a:link { + color: #b9bcff; +} + +.unapproved a:visited { + color: #696dff; +} + +.unapproved a:hover { + color: #009ef0; +} + +.approve { + display: none; +} + +.unapproved .approve { + display: inline; +} + +.unapproved .unapprove { + display: none; +} + +.updated, .confirm { + background: #CFEBF7 url(images/notice.gif) no-repeat 1em; + border: 1px solid #2580B2; + margin: 1em 5% 10px; + padding: 0 1em 0 3em; +} + +.error { + background: #FFEFF7; + border: 1px solid #c69; + margin: 1em 5% 10px; + padding: 0 1em 0 1em; +} + +.wrap { + background: #fff; + border: 1px solid #ccc; + clear: both; + margin: 15px 5%; + padding: 1em; +} + +.narrow { + width: 450px; + margin: auto; +} + +.narrow p { + line-height: 150%; +} + +.wrap h2 { + margin: .4em 0 .5em; + clear: both; +} + +* html .wrap h2 { + margin-top: 1em; +} + +table .vers { + text-align: center; +} + +textarea.all-options, input.all-options { + width: 250px; +} + +input.disabled, textarea.disabled { + background: #ccc; +} + +#adminmenu { + background: #83B4D8; + border-top: 3px solid #448abd; + margin: 0; + padding: .2em .2em .3em 2em; +} + + +#adminmenu .current, #submenu .current { + font-weight: bold; + text-decoration: none; +} + +#adminmenu a { + color: #000; + font-size: 14px; + font-weight: normal; + margin: 0; + padding: 3px 5px; + border-bottom: none; +} + +#adminmenu a:hover, #adminmenu a.current { + background: #ddeaf4; + color: #333; +} + +#adminmenu li, #submenu li { + display: inline; + line-height: 200%; + list-style: none; + text-align: center; +} + +#adminmenu a.current { + background: #0d324f; + border-right: 2px solid #4f96c8; + border-top: 1px solid #96c0de; + color: #fff; + padding-bottom: 8px; +} + +#submenu, #minisub { + background: #0d324f; + border-bottom: none; + margin: 0; + padding: 3px 2em 0 3em; +} + +#minisub { + height: 6px; +} + +#submenu .current { + background: #f9fcfe; + border-top: 1px solid #045290; + border-right: 2px solid #045290; + color: #000; +} + +#submenu a { + border: none; + color: #fff; + font-size: 12px; + padding: .3em .4em .4em; +} + +#submenu a:hover { + background: #ddeaf4; + color: #393939; +} + +#submenu li { + line-height: 180%; + height: 25px; +} + + +#categorydiv input, #poststatusdiv input, #commentstatusdiv input, #pingstatusdiv input { + border: none; +} + +#postdiv, #titlediv, #guiddiv, #linkdiv { + margin: 0 8px 0 0; + padding: 0px; +} + +#postdivrich { + margin: 0px; + padding: 0px; +} + +#content { + margin: 0 0 0 0; + width: 95%; +} + +#postdivrich #content { + padding: .7em; + line-height: 140%; +} + +#titlediv input, #guiddiv input, #linkdiv input { + margin: 0px; + width: 100%; +} + +#currenttheme img { + float: left; + border: 1px solid #666; + margin-right: 1em; + margin-bottom: 1.5em; + width: 300px; +} + +input.delete:hover { + background: #ce0000; + color: #fff; +} + +#deletebookmarks:hover { + background: #ce0000; + color: #fff; +} + +#postdivrich #quicktags { + background: #f0f0ee; + padding: 0px; + border: 1px solid #ccc; + border-bottom: none; +} + +#postdiv #quicktags { + padding-right: 6px; +} + +#postdivrich #quicktags { + display: none; +} + +#quicktags #ed_toolbar { + padding: 0px 2px; +} + +#ed_toolbar input { + background: #fff url( images/fade-butt.png ) repeat-x 0px -2px; + margin: 3px 2px 2px; +} + +#quicktags #ed_strong { + font-weight: bold; +} + +#quicktags #ed_link { + color: blue; + text-decoration: underline; +} + +#quicktags #ed_del { + text-decoration: line-through; +} + +#quicktags #ed_em { + font-style: italic; +} + +#quicktags #ed_code { + font-family: "Courier New", Courier, mono; + margin-bottom: 3px; +} + +#title { + font-size: 1.7em; + padding: 4px; +} + +#postexcerpt div, #attachmentlinks div { + margin-right: 8px; +} + +#attachmentlinks textarea { + width: 100%; + height: 2.5em; + margin-bottom: 6px; +} + +* html #postexcerpt .dbx-toggle-open, * html #postexcerpt .dbx-toggle-open { + padding-right: 8px; +} + +#excerpt, .attachmentlinks { + margin: 0px; + height: 4em; + width: 100%; +} + +#footer { + clear: both; + text-align: center; + width: 500px; + margin: auto; + height: 100px; +} + +#footer .docs { + padding-top: 19px; + line-height: 160%; +} + +#footer .docs a { + text-decoration: underline; +} +#footer .logo { + float: left; + margin: 0; + padding: 0; + font-size:0.5em; +} + +#login { + position: relative; + background: url('images/login-bkg-tile.gif') no-repeat top center; + color: #fff; + margin: 5em auto 1em; + padding: 20px 0 0; + width: 425px; +} + +#login form { + background: url('images/login-bkg-bottom.gif') no-repeat bottom center; + padding: 0 50px 25px; +} + +#login #login_error { + background: #0e3350; + border: 1px solid #2571ab; + color: #ebcd4e; + font-size: 11px; + font-weight: bold; + padding: .6em; + width: 310px; + margin: 0 auto; + text-align: center; +} + +#login p { + font-size: 12px; +} + +#login p.message { + width: 310px; + margin: 0 auto 1em; +} + +#login #login_error a { + color: #ebcd4e; + border-color: #ebcd4e; +} + +#login #send { + color: #fff; + text-align: left; + font-weight: normal; + font-size: 1.1em; + _width: 325px; + _margin: 0 auto 15px; +} + +#login h1 a { + margin: 0 auto; + height: 88px; + width: 320px; + display: block; + border-bottom: none; + text-indent: -9999px; +} + +#login .hide { + display: none; +} + +#login .message { + font-size: 10pt; + text-align: center; +} + +#login .register { + font-size: 20px; +} + +#login input { + padding: 4px; +} + +.login ul, #protected #login .bottom { + list-style: none; + width: 325px; + margin: 0 auto; + padding: 0; + line-height: 1.2; +} + +.login ul li { + font-size: 11px; +} + +.login ul li a { + color: #0d324f; + border: none; +} + +#login ul li a:hover { + color: #fff; +} + +#login .input { + font-size: 1.8em; + margin-top: 3px; + width: 97%; +} + +#login p label { + font-size: 11px; +} + +#login input#rememberme { + background-color: 0e3757; +} + +#login #submit { + margin: 0; + font-size: 15px; +} + +.plugins p { +} + +#login .fullwidth { + width: 320px; +} + +#searchform { + float: left; + margin-right: 1em; + width: 18em; +} + +#viewarc { + float: left; + width: 23em; + margin-bottom: 1em; +} + +#viewcat { + float: left; + width: 30em; + margin-bottom: 1em; +} + +#postcustom .updatemeta, #postcustom .deletemeta { + margin: auto; +} + +#postcustom table { + border: 1px solid #ccc; + margin: 0px; + width: 100%; +} + +#postcustom table input, #postcustom table textarea { + width: 95%; +} + +#poststuff { + margin-right: 16em; +} + +#save { + width: 15em; +} + +#template div { + margin-right: 190px; +} + +* html #template div { + margin-right: 0px; +} + +#template, #template div, #editcat, #addcat { + zoom: 1; +} + +#template textarea { + font: small 'Courier New', Courier, monospace; + width: 97%; +} + +#templateside { + float: right; + width: 170px; + overflow: hidden; +} + +#templateside h3, #postcustom p.submit { + margin: 0; +} + +#templateside ol, #templateside ul { + list-style: none; + margin: .5em; + padding: 0; +} + +#user_info { + position: absolute; + right: 1em; + top: 0; + color: #fff; + font-size: .9em; +} + +#user_info a { + color: #fff; +} + +#wphead { + background: #14568a; + padding: .8em 19em .8em 2em; + color: #c3def1; +} + +#wphead a { + color: #fff; +} + +#wphead h1 { + font-size: 2.5em; + font-weight: normal; + letter-spacing: -.05em; + margin: 0; + font-family: Georgia, "Times New Roman", Times, serif +} + +#wphead h1 span { + font-size: .4em; + letter-spacing: 0; +} + +#zeitgeist { + background: #eee; + border: 1px solid #c5c5c5; + float: right; + font-size: 90%; + margin-bottom: .5em; + margin-left: 1em; + margin-top: .5em; + padding: 1em; + width: 40%; +} + +#zeitgeist h2, fieldset legend a { + border-bottom: none; +} + +* html #zeitgeist h2 { + padding-top: 10px; +} + +#zeitgeist h2 { + margin-top: .4em; +} + +#zeitgeist h3 { + border-bottom: 1px solid #ccc; + font-size: 16px; + margin: 1em 0 0; +} + +#zeitgeist h3 cite { + font-size: 12px; + font-style: normal; +} + +#zeitgeist li, #zeitgeist p { + margin: .2em 0; +} + +#zeitgeist ul { + margin: 0 0 .3em .6em; + padding: 0 0 0 .6em; +} + +.active td { + background: #BEB; +} +.active .name { + background: #9C9; +} +.alternate.active td { + background: #ADA; +} +.alternate.active .name { + background: #8B8; +} + +#namediv, #emaildiv, #uridiv { + float: left; +} + +#ajax-response { + padding: .5em; +} + +/* A handy div class for hiding controls. +Some browsers will disable them when you +set display:none; */ +.zerosize { + height: 0px; + width: 0px; + margin: 0px; + border: 0px; + padding: 0px; + overflow: hidden; + position: absolute; +} + +/* Box stuff */ +.dbx-clone { + position:absolute; + visibility:hidden; +} +.dbx-clone, .dbx-clone .dbx-handle-cursor { + cursor:move !important; +} +.dbx-dummy { + display:block; + width:0; + height:0; + overflow:hidden; +} +.dbx-group, .dbx-box, .dbx-handle { + position:relative; + display:block; +} + +#grabit { + width: 188px; +} + +* html #themeselect { + padding: 0px 3px; + height: 22px; +} + +/**************************************************************** +avoid padding, margins or borders on dbx-box, +to reduce visual discrepancies between it and the clone. +overall, dbx-box is best left as visually unstyled as possible +*****************************************************************/ +.dbx-box { + margin:0; + padding:0; + border:none; +} + +/* Can change this */ +#moremeta fieldset, #advancedstuff fieldset { + margin-bottom: 0.3em; +} +#moremeta fieldset div { + margin: 2px 0 0 0px; + padding: 7px; +} +#moremeta { + line-height: 100%; + margin-right: 15px; + position: absolute; + right: 5%; + width: 14.5em; +} +#moremeta select { + width: 96%; +} + +#slugdiv input, #passworddiv input, #authordiv select, #thumbdiv input, #parentdiv input { + margin-top: .5em; + width: 90%; +} + +#moremeta h3, #advancedstuff h3 { + padding: 3px; + font-weight: normal; + font-size: 13px; +} + +#advancedstuff div { + margin-top: .5em; +} + +#categorydiv ul { + list-style: none; + padding: 0; + margin-left: 10px; +} + +#categorychecklist { + height: 12em; + overflow: auto; + margin-top: 8px; +} + +#categorychecklist li { + margin: 0; + padding: 0; +} + +#ajaxcat input { + border: 1px solid #ccc; +} + +#your-profile #rich_editing { + border: none; + background: #fff; +} + +#your-profile fieldset { + border: 1px solid #ccc; + float: left; + width: 40%; + padding: .5em 2em 1em; + margin: 1em 1em 1em 0; +} + +#your-profile fieldset input { + width: 100%; + font-size: 20px; + padding: 2px; +} + +#your-profile fieldset textarea { + width: 100%; + padding: 2px; +} + +#your-profile legend { + font-family: Georgia, "Times New Roman", Times, serif; + font-size: 22px; +} + +/* default box styles */ + +/* toggle state of inner content area */ +.dbx-box-open .dbx-content { + display: block; +} +.dbx-box-closed .dbx-content { + display: none; +} + +#moremeta .dbx-content { + background: url(images/box-butt.gif) no-repeat bottom right; + padding-bottom: 10px; + padding-right: 2px; +} + +#moremeta fieldset.dbx-box-closed { + background: url(images/box-butt.gif) no-repeat bottom; + padding-bottom: 9px; +} + +/* handles */ + +.dbx-handle { + background: #2685af; + padding: 6px 1em 2px; + font-size: 12px; + margin: 0; + color: #E3EFF5; +} + +#moremeta .dbx-handle { + padding: 6px 1em 2px; + font-size: 12px; + background: #2685af url(images/box-head.gif) no-repeat right; +} + +#moremeta .dbx-box { + background: url(images/box-bg.gif) repeat-y right; +} + +#advancedstuff h3.dbx-handle { + margin-left: 7px; + margin-bottom: -7px; + padding: 6px 1em 0 3px; + height: 19px; + font-size: 12px; + background: #2685af url(images/box-head-right.gif) no-repeat top right; +} + +#advancedstuff div.dbx-handle-wrapper { + margin: 0 0 0 -7px; + background: #fff url(images/box-head-left.gif) no-repeat top left; +} + +#advancedstuff div.dbx-content { + margin-left: 8px; + background: url(images/box-bg-right.gif) repeat-y right; + padding: 10px 10px 15px 0px; +} + +#postexcerpt div.dbx-content { + margin-right: 0; + padding-right: 17px; +} + +#advancedstuff div.dbx-content-wrapper { + margin-left: -7px; + margin-right: 0; + background: url(images/box-bg-left.gif) repeat-y left; +} + +#advancedstuff fieldset.dbx-box { + padding-bottom: 9px; + margin-left: 6px; + background: url(images/box-butt-right.gif) no-repeat bottom right; +} + +#advancedstuff div.dbx-box-wrapper { + background: url(images/box-butt-left.gif) no-repeat bottom left; +} + +#advancedstuff .dbx-box-closed div.dbx-content-wrapper { + padding-bottom: 2px; + background: url(images/box-butt-left.gif) no-repeat bottom left; +} + +#advancedstuff .dbx-box { + background: url(images/box-butt-right.gif) no-repeat bottom right; +} + + +/* handle cursors */ +.dbx-handle-cursor { + cursor: move; +} + +/* toggle images */ +a.dbx-toggle, a.dbx-toggle:visited { + display:block; + overflow: hidden; + background-image: url( images/toggle.gif ); + position: absolute; + top: 0px; + right: 0px; + background-repeat: no-repeat; + border: 0px; + margin: 0px; + padding: 0px; +} + +#moremeta a.dbx-toggle, #moremeta a.dbx-toggle-open:visited { + height: 25px; + width: 27px; + background-position: 0 0px; +} + +#moremeta a.dbx-toggle-open, #moremeta a.dbx-toggle-open:visited { + height: 25px; + width: 27px; + background-position: 0 -25px; +} + +#advancedstuff a.dbx-toggle, #advancedstuff a.dbx-toggle-open:visited { + height: 22px; + width: 22px; + top: 3px; + right: 5px; + background-position: 0 -3px; +} + +#advancedstuff a.dbx-toggle-open, #advancedstuff a.dbx-toggle-open:visited { + height: 22px; + width: 22px; + top: 3px; + right: 5px; + background-position: 0 -28px; +} + +#categorychecklist { + margin-right: 6px; +} + +/* additional clone styles */ +.dbx-clone { + opacity: 0.8; + -moz-opacity: 0.8; + -khtml-opacity: 0.8; + filter: alpha(opacity=80); +} + +#newcat { + width: 120px; + margin-right: 5px; +} + +input #catadd { + background: #a4a4a4; + border-bottom: 1px solid #898989; + border-left: 1px solid #bcbcbc; + border-right: 1px solid #898989; + border-top: 1px solid #bcbcbc; + color: #fff; + font-size: 10px; + padding: 0; + margin: 0; + font-weight: bold; + height: 20px; + margin-bottom: 2px; + text-align: center; + width: 37px; +} + +#howto { + font-size: 11px; + margin: 0 5px; + display: block; +} + +#jaxcat { + margin: 0; + padding: 0; +} + +#ajax-response.alignleft { + margin-left: 2em; +} + +#postdivrich #edButtons { + padding-left: 3px; +} + +#postdivrich #content, #postdivrich #content:active { + border: 1px solid #ccc; +} + +#edButtons input, #edButtons input:active { + margin: 0px 2px -1px; +} + +#edButtons input.edButtonFore, #edButtons input.edButtonFore:active { + background: #f0f0ee; + border-bottom: 1px solid #f0f0ee; +} + +#edButtons input.edButtonBack, #edButtons input.edButtonBack:active { + background: #fff url( images/fade-butt.png ) repeat-x 0px 15px; + border-bottom: 1px solid #ccc; +} + +.page-numbers { + padding: 4px 7px; + border: 1px solid #fff; + margin-right: 3px; +} + +a.page-numbers { + border: 1px solid #ccc; +} + +a.page-numbers:hover { + border: 1px solid #999; +} + +.page-numbers.current { + border: 1px solid #999; + font-weight: bold; +} + +.pagenav span { + font-weight: bold; + margin: 0 6px; +} + +ul.historic { + margin-bottom: 1em; +} \ No newline at end of file+ + + + + +Date +Contributor +Section +Action +Level +Message +> + + + +logdate; ?> +cname; ?> +section; ?> +action; ?> +level ?> +message; ?> +