diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..2ebeb60 --- /dev/null +++ b/.htaccess @@ -0,0 +1,3 @@ +RewriteEngine On + +RewriteRule ^(.*)\.xml$ rss-$1.php diff --git a/CalendarPopup.js b/CalendarPopup.js new file mode 100644 index 0000000..acb306e --- /dev/null +++ b/CalendarPopup.js @@ -0,0 +1,1465 @@ +// =================================================================== +// Author: Matt Kruse +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// =================================================================== + + +/* SOURCE FILE: AnchorPosition.js */ + +/* +AnchorPosition.js +Author: Matt Kruse +Last modified: 10/11/02 + +DESCRIPTION: These functions find the position of an tag in a document, +so other elements can be positioned relative to it. + +COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. + +FUNCTIONS: +getAnchorPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor. Position is relative to the PAGE. + +getAnchorWindowPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor, relative to the WHOLE SCREEN. + +NOTES: + +1) For popping up separate browser windows, use getAnchorWindowPosition. + Otherwise, use getAnchorPosition + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. +*/ + +// getAnchorPosition(anchorname) +// This function returns an object having .x and .y properties which are the coordinates +// of the named anchor, relative to the page. +function getAnchorPosition(anchorname) { + // This function will return an Object with x and y properties + var useWindow=false; + var coordinates=new Object(); + var x=0,y=0; + // Browser capability sniffing + var use_gebi=false, use_css=false, use_layers=false; + if (document.getElementById) { use_gebi=true; } + else if (document.all) { use_css=true; } + else if (document.layers) { use_layers=true; } + // Logic to find position + if (use_gebi && document.all) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_gebi) { + var o=document.getElementById(anchorname); + x=AnchorPosition_getPageOffsetLeft(o); + y=AnchorPosition_getPageOffsetTop(o); + } + else if (use_css) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_layers) { + var found=0; + for (var i=0; i9?"":"0")+x} + +// ------------------------------------------------------------------ +// isDate ( date_string, format_string ) +// Returns true if date string matches format of format string and +// is a valid date. Else returns false. +// It is recommended that you trim whitespace around the value before +// passing it to this function, as whitespace is NOT ignored! +// ------------------------------------------------------------------ +function isDate(val,format) { + var date=getDateFromFormat(val,format); + if (date==0) { return false; } + return true; + } + +// ------------------------------------------------------------------- +// compareDates(date1,date1format,date2,date2format) +// Compare two date strings to see which is greater. +// Returns: +// 1 if date1 is greater than date2 +// 0 if date2 is greater than date1 of if they are the same +// -1 if either of the dates is in an invalid format +// ------------------------------------------------------------------- +function compareDates(date1,dateformat1,date2,dateformat2) { + var d1=getDateFromFormat(date1,dateformat1); + var d2=getDateFromFormat(date2,dateformat2); + if (d1==0 || d2==0) { + return -1; + } + else if (d1 > d2) { + return 1; + } + return 0; + } + +// ------------------------------------------------------------------ +// formatDate (date_object, format) +// Returns a date in the output format specified. +// The format string uses the same abbreviations as in getDateFromFormat() +// ------------------------------------------------------------------ +function formatDate(date,format) { + format=format+""; + var result=""; + var i_format=0; + var c=""; + var token=""; + var y=date.getYear()+""; + var M=date.getMonth()+1; + var d=date.getDate(); + var E=date.getDay(); + var H=date.getHours(); + var m=date.getMinutes(); + var s=date.getSeconds(); + var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k; + // Convert real date parts into formatted versions + var value=new Object(); + if (y.length < 4) {y=""+(y-0+1900);} + value["y"]=""+y; + value["yyyy"]=y; + value["yy"]=y.substring(2,4); + value["M"]=M; + value["MM"]=LZ(M); + value["MMM"]=MONTH_NAMES[M-1]; + value["NNN"]=MONTH_NAMES[M+11]; + value["d"]=d; + value["dd"]=LZ(d); + value["E"]=DAY_NAMES[E+7]; + value["EE"]=DAY_NAMES[E]; + value["H"]=H; + value["HH"]=LZ(H); + if (H==0){value["h"]=12;} + else if (H>12){value["h"]=H-12;} + else {value["h"]=H;} + value["hh"]=LZ(value["h"]); + if (H>11){value["K"]=H-12;} else {value["K"]=H;} + value["k"]=H+1; + value["KK"]=LZ(value["K"]); + value["kk"]=LZ(value["k"]); + if (H > 11) { value["a"]="PM"; } + else { value["a"]="AM"; } + value["m"]=m; + value["mm"]=LZ(m); + value["s"]=s; + value["ss"]=LZ(s); + while (i_format < format.length) { + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + if (value[token] != null) { result=result + value[token]; } + else { result=result + token; } + } + return result; + } + +// ------------------------------------------------------------------ +// Utility functions for parsing in getDateFromFormat() +// ------------------------------------------------------------------ +function _isInteger(val) { + var digits="1234567890"; + for (var i=0; i < val.length; i++) { + if (digits.indexOf(val.charAt(i))==-1) { return false; } + } + return true; + } +function _getInt(str,i,minlength,maxlength) { + for (var x=maxlength; x>=minlength; x--) { + var token=str.substring(i,i+x); + if (token.length < minlength) { return null; } + if (_isInteger(token)) { return token; } + } + return null; + } + +// ------------------------------------------------------------------ +// getDateFromFormat( date_string , format_string ) +// +// This function takes a date string and a format string. It matches +// If the date string matches the format string, it returns the +// getTime() of the date. If it does not match, it returns 0. +// ------------------------------------------------------------------ +function getDateFromFormat(val,format) { + val=val+""; + format=format+""; + var i_val=0; + var i_format=0; + var c=""; + var token=""; + var token2=""; + var x,y; + var now=new Date(); + var year=now.getYear(); + var month=now.getMonth()+1; + var date=1; + var hh=now.getHours(); + var mm=now.getMinutes(); + var ss=now.getSeconds(); + var ampm=""; + + while (i_format < format.length) { + // Get next token from format string + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + // Extract contents of value based on format token + if (token=="yyyy" || token=="yy" || token=="y") { + if (token=="yyyy") { x=4;y=4; } + if (token=="yy") { x=2;y=2; } + if (token=="y") { x=2;y=4; } + year=_getInt(val,i_val,x,y); + if (year==null) { return 0; } + i_val += year.length; + if (year.length==2) { + if (year > 70) { year=1900+(year-0); } + else { year=2000+(year-0); } + } + } + else if (token=="MMM"||token=="NNN"){ + month=0; + for (var i=0; i11)) { + month=i+1; + if (month>12) { month -= 12; } + i_val += month_name.length; + break; + } + } + } + if ((month < 1)||(month>12)){return 0;} + } + else if (token=="EE"||token=="E"){ + for (var i=0; i12)){return 0;} + i_val+=month.length;} + else if (token=="dd"||token=="d") { + date=_getInt(val,i_val,token.length,2); + if(date==null||(date<1)||(date>31)){return 0;} + i_val+=date.length;} + else if (token=="hh"||token=="h") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>12)){return 0;} + i_val+=hh.length;} + else if (token=="HH"||token=="H") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>23)){return 0;} + i_val+=hh.length;} + else if (token=="KK"||token=="K") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>11)){return 0;} + i_val+=hh.length;} + else if (token=="kk"||token=="k") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>24)){return 0;} + i_val+=hh.length;hh--;} + else if (token=="mm"||token=="m") { + mm=_getInt(val,i_val,token.length,2); + if(mm==null||(mm<0)||(mm>59)){return 0;} + i_val+=mm.length;} + else if (token=="ss"||token=="s") { + ss=_getInt(val,i_val,token.length,2); + if(ss==null||(ss<0)||(ss>59)){return 0;} + i_val+=ss.length;} + else if (token=="a") { + if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";} + else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";} + else {return 0;} + i_val+=2;} + else { + if (val.substring(i_val,i_val+token.length)!=token) {return 0;} + else {i_val+=token.length;} + } + } + // If there are any trailing characters left in the value, it doesn't match + if (i_val != val.length) { return 0; } + // Is date valid for month? + if (month==2) { + // Check for leap year + if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year + if (date > 29){ return 0; } + } + else { if (date > 28) { return 0; } } + } + if ((month==4)||(month==6)||(month==9)||(month==11)) { + if (date > 30) { return 0; } + } + // Correct hours value + if (hh<12 && ampm=="PM") { hh=hh-0+12; } + else if (hh>11 && ampm=="AM") { hh-=12; } + var newdate=new Date(year,month-1,date,hh,mm,ss); + return newdate.getTime(); + } + +// ------------------------------------------------------------------ +// parseDate( date_string [, prefer_euro_format] ) +// +// This function takes a date string and tries to match it to a +// number of possible date formats to get the value. It will try to +// match against the following international formats, in this order: +// y-M-d MMM d, y MMM d,y y-MMM-d d-MMM-y MMM d +// M/d/y M-d-y M.d.y MMM-d M/d M-d +// d/M/y d-M-y d.M.y d-MMM d/M d-M +// A second argument may be passed to instruct the method to search +// for formats like d/M/y (european format) before M/d/y (American). +// Returns a Date object or null if no patterns match. +// ------------------------------------------------------------------ +function parseDate(val) { + var preferEuro=(arguments.length==2)?arguments[1]:false; + generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d'); + monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d'); + dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M'); + var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst'); + var d=null; + for (var i=0; i tags may cause errors. + +USAGE: +// Create an object for a WINDOW popup +var win = new PopupWindow(); + +// Create an object for a DIV window using the DIV named 'mydiv' +var win = new PopupWindow('mydiv'); + +// Set the window to automatically hide itself when the user clicks +// anywhere else on the page except the popup +win.autoHide(); + +// Show the window relative to the anchor name passed in +win.showPopup(anchorname); + +// Hide the popup +win.hidePopup(); + +// Set the size of the popup window (only applies to WINDOW popups +win.setSize(width,height); + +// Populate the contents of the popup window that will be shown. If you +// change the contents while it is displayed, you will need to refresh() +win.populate(string); + +// set the URL of the window, rather than populating its contents +// manually +win.setUrl("http://www.site.com/"); + +// Refresh the contents of the popup +win.refresh(); + +// Specify how many pixels to the right of the anchor the popup will appear +win.offsetX = 50; + +// Specify how many pixels below the anchor the popup will appear +win.offsetY = 100; + +NOTES: +1) Requires the functions in AnchorPosition.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a PopupWindow object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a PopupWindow object or + the autoHide() will not work correctly. +*/ + +// Set the position of the popup window based on the anchor +function PopupWindow_getXYPosition(anchorname) { + var coordinates; + if (this.type == "WINDOW") { + coordinates = getAnchorWindowPosition(anchorname); + } + else { + coordinates = getAnchorPosition(anchorname); + } + this.x = coordinates.x; + this.y = coordinates.y; + } +// Set width/height of DIV/popup window +function PopupWindow_setSize(width,height) { + this.width = width; + this.height = height; + } +// Fill the window with contents +function PopupWindow_populate(contents) { + this.contents = contents; + this.populated = false; + } +// Set the URL to go to +function PopupWindow_setUrl(url) { + this.url = url; + } +// Set the window popup properties +function PopupWindow_setWindowProperties(props) { + this.windowProperties = props; + } +// Refresh the displayed contents of the popup +function PopupWindow_refresh() { + if (this.divName != null) { + // refresh the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).innerHTML = this.contents; + } + else if (this.use_css) { + document.all[this.divName].innerHTML = this.contents; + } + else if (this.use_layers) { + var d = document.layers[this.divName]; + d.document.open(); + d.document.writeln(this.contents); + d.document.close(); + } + } + else { + if (this.popupWindow != null && !this.popupWindow.closed) { + if (this.url!="") { + this.popupWindow.location.href=this.url; + } + else { + this.popupWindow.document.open(); + this.popupWindow.document.writeln(this.contents); + this.popupWindow.document.close(); + } + this.popupWindow.focus(); + } + } + } +// Position and show the popup, relative to an anchor object +function PopupWindow_showPopup(anchorname) { + this.getXYPosition(anchorname); + this.x += this.offsetX; + this.y += this.offsetY; + if (!this.populated && (this.contents != "")) { + this.populated = true; + this.refresh(); + } + if (this.divName != null) { + // Show the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).style.left = this.x + "px"; + document.getElementById(this.divName).style.top = this.y + "px"; + document.getElementById(this.divName).style.visibility = "visible"; + } + else if (this.use_css) { + document.all[this.divName].style.left = this.x; + document.all[this.divName].style.top = this.y; + document.all[this.divName].style.visibility = "visible"; + } + else if (this.use_layers) { + document.layers[this.divName].left = this.x; + document.layers[this.divName].top = this.y; + document.layers[this.divName].visibility = "visible"; + } + } + else { + if (this.popupWindow == null || this.popupWindow.closed) { + // If the popup window will go off-screen, move it so it doesn't + if (this.x<0) { this.x=0; } + if (this.y<0) { this.y=0; } + if (screen && screen.availHeight) { + if ((this.y + this.height) > screen.availHeight) { + this.y = screen.availHeight - this.height; + } + } + if (screen && screen.availWidth) { + if ((this.x + this.width) > screen.availWidth) { + this.x = screen.availWidth - this.width; + } + } + var avoidAboutBlank = window.opera || ( document.layers && !navigator.mimeTypes['*'] ) || navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ); + this.popupWindow = window.open(avoidAboutBlank?"":"about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+""); + } + this.refresh(); + } + } +// Hide the popup +function PopupWindow_hidePopup() { + if (this.divName != null) { + if (this.use_gebi) { + document.getElementById(this.divName).style.visibility = "hidden"; + } + else if (this.use_css) { + document.all[this.divName].style.visibility = "hidden"; + } + else if (this.use_layers) { + document.layers[this.divName].visibility = "hidden"; + } + } + else { + if (this.popupWindow && !this.popupWindow.closed) { + this.popupWindow.close(); + this.popupWindow = null; + } + } + } +// Pass an event and return whether or not it was the popup DIV that was clicked +function PopupWindow_isClicked(e) { + if (this.divName != null) { + if (this.use_layers) { + var clickX = e.pageX; + var clickY = e.pageY; + var t = document.layers[this.divName]; + if ((clickX > t.left) && (clickX < t.left+t.clip.width) && (clickY > t.top) && (clickY < t.top+t.clip.height)) { + return true; + } + else { return false; } + } + else if (document.all) { // Need to hard-code this to trap IE for error-handling + var t = window.event.srcElement; + while (t.parentElement != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentElement; + } + return false; + } + else if (this.use_gebi && e) { + var t = e.originalTarget; + while (t.parentNode != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentNode; + } + return false; + } + return false; + } + return false; + } + +// Check an onMouseDown event to see if we should hide +function PopupWindow_hideIfNotClicked(e) { + if (this.autoHideEnabled && !this.isClicked(e)) { + this.hidePopup(); + } + } +// Call this to make the DIV disable automatically when mouse is clicked outside it +function PopupWindow_autoHide() { + this.autoHideEnabled = true; + } +// This global function checks all PopupWindow objects onmouseup to see if they should be hidden +function PopupWindow_hidePopupWindows(e) { + for (var i=0; i0) { + this.type="DIV"; + this.divName = arguments[0]; + } + else { + this.type="WINDOW"; + } + this.use_gebi = false; + this.use_css = false; + this.use_layers = false; + if (document.getElementById) { this.use_gebi = true; } + else if (document.all) { this.use_css = true; } + else if (document.layers) { this.use_layers = true; } + else { this.type = "WINDOW"; } + this.offsetX = 0; + this.offsetY = 0; + // Method mappings + this.getXYPosition = PopupWindow_getXYPosition; + this.populate = PopupWindow_populate; + this.setUrl = PopupWindow_setUrl; + this.setWindowProperties = PopupWindow_setWindowProperties; + this.refresh = PopupWindow_refresh; + this.showPopup = PopupWindow_showPopup; + this.hidePopup = PopupWindow_hidePopup; + this.setSize = PopupWindow_setSize; + this.isClicked = PopupWindow_isClicked; + this.autoHide = PopupWindow_autoHide; + this.hideIfNotClicked = PopupWindow_hideIfNotClicked; + } + +/* SOURCE FILE: CalendarPopup.js */ + +// HISTORY +// ------------------------------------------------------------------ +// Feb 7, 2005: Fixed a CSS styles to use px unit +// March 29, 2004: Added check in select() method for the form field +// being disabled. If it is, just return and don't do anything. +// March 24, 2004: Fixed bug - when month name and abbreviations were +// changed, date format still used original values. +// January 26, 2004: Added support for drop-down month and year +// navigation (Thanks to Chris Reid for the idea) +// September 22, 2003: Fixed a minor problem in YEAR calendar with +// CSS prefix. +// August 19, 2003: Renamed the function to get styles, and made it +// work correctly without an object reference +// August 18, 2003: Changed showYearNavigation and +// showYearNavigationInput to optionally take an argument of +// true or false +// July 31, 2003: Added text input option for year navigation. +// Added a per-calendar CSS prefix option to optionally use +// different styles for different calendars. +// July 29, 2003: Fixed bug causing the Today link to be clickable +// even though today falls in a disabled date range. +// Changed formatting to use pure CSS, allowing greater control +// over look-and-feel options. +// June 11, 2003: Fixed bug causing the Today link to be unselectable +// under certain cases when some days of week are disabled +// March 14, 2003: Added ability to disable individual dates or date +// ranges, display as light gray and strike-through +// March 14, 2003: Removed dependency on graypixel.gif and instead +/// use table border coloring +// March 12, 2003: Modified showCalendar() function to allow optional +// start-date parameter +// March 11, 2003: Modified select() function to allow optional +// start-date parameter +/* +DESCRIPTION: This object implements a popup calendar to allow the user to +select a date, month, quarter, or year. + +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. +The calendar can be modified to work for any location in the world by +changing which weekday is displayed as the first column, changing the month +names, and changing the column headers for each day. + +USAGE: +// Create a new CalendarPopup object of type WINDOW +var cal = new CalendarPopup(); + +// Create a new CalendarPopup object of type DIV using the DIV named 'mydiv' +var cal = new CalendarPopup('mydiv'); + +// Easy method to link the popup calendar with an input box. +cal.select(inputObject, anchorname, dateFormat); +// Same method, but passing a default date other than the field's current value +cal.select(inputObject, anchorname, dateFormat, '01/02/2000'); +// This is an example call to the popup calendar from a link to populate an +// input box. Note that to use this, date.js must also be included!! +Select + +// Set the type of date select to be used. By default it is 'date'. +cal.setDisplayType(type); + +// When a date, month, quarter, or year is clicked, a function is called and +// passed the details. You must write this function, and tell the calendar +// popup what the function name is. +// Function to be called for 'date' select receives y, m, d +cal.setReturnFunction(functionname); +// Function to be called for 'month' select receives y, m +cal.setReturnMonthFunction(functionname); +// Function to be called for 'quarter' select receives y, q +cal.setReturnQuarterFunction(functionname); +// Function to be called for 'year' select receives y +cal.setReturnYearFunction(functionname); + +// Show the calendar relative to a given anchor +cal.showCalendar(anchorname); + +// Hide the calendar. The calendar is set to autoHide automatically +cal.hideCalendar(); + +// Set the month names to be used. Default are English month names +cal.setMonthNames("January","February","March",...); + +// Set the month abbreviations to be used. Default are English month abbreviations +cal.setMonthAbbreviations("Jan","Feb","Mar",...); + +// Show navigation for changing by the year, not just one month at a time +cal.showYearNavigation(); + +// Show month and year dropdowns, for quicker selection of month of dates +cal.showNavigationDropdowns(); + +// Set the text to be used above each day column. The days start with +// sunday regardless of the value of WeekStartDay +cal.setDayHeaders("S","M","T",...); + +// Set the day for the first column in the calendar grid. By default this +// is Sunday (0) but it may be changed to fit the conventions of other +// countries. +cal.setWeekStartDay(1); // week is Monday - Sunday + +// Set the weekdays which should be disabled in the 'date' select popup. You can +// then allow someone to only select week end dates, or Tuedays, for example +cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week + +// Selectively disable individual days or date ranges. Disabled days will not +// be clickable, and show as strike-through text on current browsers. +// Date format is any format recognized by parseDate() in date.js +// Pass a single date to disable: +cal.addDisabledDates("2003-01-01"); +// Pass null as the first parameter to mean "anything up to and including" the +// passed date: +cal.addDisabledDates(null, "01/02/03"); +// Pass null as the second parameter to mean "including the passed date and +// anything after it: +cal.addDisabledDates("Jan 01, 2003", null); +// Pass two dates to disable all dates inbetween and including the two +cal.addDisabledDates("January 01, 2003", "Dec 31, 2003"); + +// When the 'year' select is displayed, set the number of years back from the +// current year to start listing years. Default is 2. +// This is also used for year drop-down, to decide how many years +/- to display +cal.setYearSelectStartOffset(2); + +// Text for the word "Today" appearing on the calendar +cal.setTodayText("Today"); + +// The calendar uses CSS classes for formatting. If you want your calendar to +// have unique styles, you can set the prefix that will be added to all the +// classes in the output. +// For example, normal output may have this: +// Today +// But if you set the prefix like this: +cal.setCssPrefix("Test"); +// The output will then look like: +// Today +// And you can define that style somewhere in your page. + +// When using Year navigation, you can make the year be an input box, so +// the user can manually change it and jump to any year +cal.showYearNavigationInput(); + +// Set the calendar offset to be different than the default. By default it +// will appear just below and to the right of the anchorname. So if you have +// a text box where the date will go and and anchor immediately after the +// text box, the calendar will display immediately under the text box. +cal.offsetX = 20; +cal.offsetY = 20; + +NOTES: +1) Requires the functions in AnchorPosition.js and PopupWindow.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a CalendarPopup object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a CalendarPopup object + or the autoHide() will not work correctly. + +5) The calendar popup display uses style sheets to make it look nice. + +*/ + +// Quick fix for FF3 +function CP_stop(e) { if (e && e.stopPropagation) { e.stopPropagation(); } } + +// CONSTRUCTOR for the CalendarPopup Object +function CalendarPopup() { + var c; + if (arguments.length>0) { + c = new PopupWindow(arguments[0]); + } + else { + c = new PopupWindow(); + c.setSize(150,175); + } + c.offsetX = -152; + c.offsetY = 25; + c.autoHide(); + // Calendar-specific properties + c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December"); + c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); + c.dayHeaders = new Array("S","M","T","W","T","F","S"); + c.returnFunction = "CP_tmpReturnFunction"; + c.returnMonthFunction = "CP_tmpReturnMonthFunction"; + c.returnQuarterFunction = "CP_tmpReturnQuarterFunction"; + c.returnYearFunction = "CP_tmpReturnYearFunction"; + c.weekStartDay = 0; + c.isShowYearNavigation = false; + c.displayType = "date"; + c.disabledWeekDays = new Object(); + c.disabledDatesExpression = ""; + c.yearSelectStartOffset = 2; + c.currentDate = null; + c.todayText="Today"; + c.cssPrefix=""; + c.isShowNavigationDropdowns=false; + c.isShowYearNavigationInput=false; + window.CP_calendarObject = null; + window.CP_targetInput = null; + window.CP_dateFormat = "MM/dd/yyyy"; + // Method mappings + c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow; + c.setReturnFunction = CP_setReturnFunction; + c.setReturnMonthFunction = CP_setReturnMonthFunction; + c.setReturnQuarterFunction = CP_setReturnQuarterFunction; + c.setReturnYearFunction = CP_setReturnYearFunction; + c.setMonthNames = CP_setMonthNames; + c.setMonthAbbreviations = CP_setMonthAbbreviations; + c.setDayHeaders = CP_setDayHeaders; + c.setWeekStartDay = CP_setWeekStartDay; + c.setDisplayType = CP_setDisplayType; + c.setDisabledWeekDays = CP_setDisabledWeekDays; + c.addDisabledDates = CP_addDisabledDates; + c.setYearSelectStartOffset = CP_setYearSelectStartOffset; + c.setTodayText = CP_setTodayText; + c.showYearNavigation = CP_showYearNavigation; + c.showCalendar = CP_showCalendar; + c.hideCalendar = CP_hideCalendar; + c.getStyles = getCalendarStyles; + c.refreshCalendar = CP_refreshCalendar; + c.getCalendar = CP_getCalendar; + c.select = CP_select; + c.setCssPrefix = CP_setCssPrefix; + c.showNavigationDropdowns = CP_showNavigationDropdowns; + c.showYearNavigationInput = CP_showYearNavigationInput; + c.copyMonthNamesToWindow(); + // Return the object + return c; + } +function CP_copyMonthNamesToWindow() { + // Copy these values over to the date.js + if (typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null) { + window.MONTH_NAMES = new Array(); + for (var i=0; i\n"; + result += '
\n'; + } + else { + result += '
\n'; + result += '
\n'; + result += '
\n'; + } + // Code for DATE display (default) + // ------------------------------- + if (this.displayType=="date" || this.displayType=="week-end") { + if (this.currentDate==null) { this.currentDate = now; } + if (arguments.length > 0) { var month = arguments[0]; } + else { var month = this.currentDate.getMonth()+1; } + if (arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]) { var year = arguments[1]; } + else { var year = this.currentDate.getFullYear(); } + var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31); + if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) { + daysinmonth[2] = 29; + } + var current_month = new Date(year,month-1,1); + var display_year = year; + var display_month = month; + var display_date = 1; + var weekday= current_month.getDay(); + var offset = 0; + + offset = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ; + if (offset > 0) { + display_month--; + if (display_month < 1) { display_month = 12; display_year--; } + display_date = daysinmonth[display_month]-offset+1; + } + var next_month = month+1; + var next_month_year = year; + if (next_month > 12) { next_month=1; next_month_year++; } + var last_month = month-1; + var last_month_year = year; + if (last_month < 1) { last_month=12; last_month_year--; } + var date_class; + if (this.type!="WINDOW") { + result += ""; + } + 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 += '
 <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
\n'; + result += '\n'; + result += '\n'; + for (var j=0; j<7; j++) { + + result += '\n'; + } + result += '\n'; + for (var row=1; row<=6; row++) { + 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 += ' \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 += ' \n'; + } + display_date++; + if (display_date > daysinmonth[display_month]) { + display_date=1; + display_month++; + } + if (display_month > 12) { + display_month=1; + display_year++; + } + } + result += ''; + } + var current_weekday = now.getDay() - this.weekStartDay; + if (current_weekday < 0) { + current_weekday += 7; + } + result += '\n'; + result += '
'+this.dayHeaders[(this.weekStartDay+j)%7]+'
'+display_date+''+display_date+'
\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 += '
\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 += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += ' \n'; + result += '
<<'+year+'>>
\n'; + } + } + + // Code for MONTH display + // ---------------------- + if (this.displayType=="month") { + // If POPUP, write entire HTML document + result += '\n'; + for (var i=0; i<4; i++) { + result += ''; + for (var j=0; j<3; j++) { + var monthindex = ((i*3)+j); + result += ''; + } + result += ''; + } + result += '
'+this.monthAbbreviations[monthindex]+'
\n'; + } + + // Code for QUARTER display + // ------------------------ + if (this.displayType=="quarter") { + 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+'

\n'; + } + + // Code for YEAR display + // --------------------- + if (this.displayType=="year") { + var yearColumnSize = 4; + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += '
<<>>
\n'; + result += '\n'; + for (var i=0; i'+currentyear+''; + } + result += ''; + } + result += '
\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

+
+ + +

+

+ +At: + + + select +

+ +

+
+ + + + + + + + + + + + + + > + + + + + + + +
UserTweetTime
username; ?>text; ?>time); ?>Delete
+ + + + \ No newline at end of file diff --git a/delete-comic.php b/delete-comic.php new file mode 100644 index 0000000..ad20caa --- /dev/null +++ b/delete-comic.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/delete-page.php b/delete-page.php new file mode 100644 index 0000000..14bbf01 --- /dev/null +++ b/delete-page.php @@ -0,0 +1,19 @@ + diff --git a/delete-rant.php b/delete-rant.php new file mode 100644 index 0000000..9fdee71 --- /dev/null +++ b/delete-rant.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/delete-tweet.php b/delete-tweet.php new file mode 100644 index 0000000..399e6ea --- /dev/null +++ b/delete-tweet.php @@ -0,0 +1,26 @@ +query("DELETE FROM twitter_post WHERE id = '$victim'"); + if(!$r) + { + adminlog('Error deleting scheduled tweet ' . $victim, MTS_TWITTER, MTA_DELETE, E_ERROR); + mtdie('Error deleting the specified tweet.', 'SQL Error'); + } +} + +adminlog("Tweet $victim deleted.", MTS_TWITTER, MTA_DELETE); +_redirect( ADMIN_PATH . '/character-twitter.php?deleted=success' ); + +?> \ No newline at end of file diff --git a/delete-twitter-user.php b/delete-twitter-user.php new file mode 100644 index 0000000..8609ecf --- /dev/null +++ b/delete-twitter-user.php @@ -0,0 +1,26 @@ +query("DELETE FROM twitter_user WHERE id = '$victim'"); + if(!$r) + { + adminlog('Error deleting specified twitter user ' . $victim, MTS_TWITTER, MTA_DELETE, E_ERROR); + mtdie('Error deleting the specified twitter user.', 'SQL Error'); + } +} + +adminlog("Twitter $victim deleted.", MTS_TWITTER, MTA_DELETE); +_redirect( ADMIN_PATH . '/manage-twitter-users.php?deleted=success' ); + +?> \ No newline at end of file diff --git a/edit-comic.php b/edit-comic.php new file mode 100644 index 0000000..6d526fe --- /dev/null +++ b/edit-comic.php @@ -0,0 +1,258 @@ +id = (int)$_REQUEST['strip_id']; +$strip = getstrip($strip->id); + +if( $_POST ) { + + // Form Elements + $strip->new_id = (int)$_POST['strip_new_id'] ? (int)$_POST['strip_new_id'] : $strip->id; + $strip->published = empty($_POST['strip_date']) ? time() : strtotime( $_POST['strip_date'] ); + $strip->type = (int)$_POST['strip_type']; + $strip->title = trim($_POST['strip_title']); + $strip->transcript_posted = $_POST['content']; + $strip->book = trim($_POST['book']); + $strip->page = trim($_POST['page']); + + if( '' == $strip->title ) mtdie('Strips must be supplied with titles.'); + + $YESTERDAY = mktime(0,0,0, date('m'), date('d')-1, date('Y')); + + switch($_POST['action']) { + case 'new_comic': + + check_nonce('new-strip'); + + // Ensure that dates are not in the past + if( $strip->published < $YESTERDAY ) + mtdie('Strips may not be backdated. Enter a date today or in the future.'); + + if( !is_valid_upload('comicFile') ) + { + adminlog("Image upload failed.", MTS_STRIP, MTA_ADD, E_WARNING); + mtdie('If you want to upload a new comic, you must provide said comic.','Strip upload failed.'); + } + + // get image type and target extension + $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 new strip. Invalid media type.", MTS_STRIP, MTA_ADD, E_ERROR); + mtdie('Bad image type upload on new strip. Invalid media type.'); + } + + // Insert new strip into the database, get a real $strip->id + if(!insertstrip( $strip )) + { + adminlog("Error on insertion of new strip: ".mysql_error(), MTS_STRIP, MTA_ADD, E_ERROR); + mtdie('Error on insertion of new strip: '.mysql_error(), 'SQL Error'); + } + + // Store the uploaded file to xxxx-0.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 storing image.", MTS_STRIP, MTA_ADD, E_ERROR); + mtdie('There was a problem storing the uploaded strip image. Please harass contact support.','Filesystem Error'); + } + + // Alert Twitter if the strip is for immediate publication + if( $strip->published <= time() ) + twitterpost("Comic ".$strip->id." posted: ".SITE_HOST.SITE_PATH."/strip/".$strip->id); + + $info.="

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. Please harass contact support.", 'Filesystem Error'); + } + adminlog("Image replaced for comic ".$strip->id.".", MTS_STRIP, MTA_UPDATE); + } + // No new file uploaded. Do nuffink. + + // Conditionally broadcast success + if( $_POST['broadcast'] ) { + #Limit broadcast message to 60 characters. Compose it now. + $b_msg = 'Comic ' . $strip->id . ' updated: ' . substr(trim($_REQUEST['broadcast_message']), 0, 60) . ', ' . SITE_HOST . SITE_PATH . "/strip/" . $strip->id; + + rsspost($b_msg, SITE_HOST.SITE_PATH.'/strip/'.$strip->id); + twitterpost($b_msg); + $info.="

Update 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'); + +?> + +

Edit Comic

+ +
+id); ?> + + +
+ +
+
+ +
+

Comic Type

+
+
+ +
+

Post Date

+
+
+ +
+

Strip Number

+
+
+ +
+

Replace Comic

+
+
+ +
+

Broadcast Update

+
+ + + +
+
+ + +
+

Strip

+
+
+ + +
+

Book Page Number

+
-
+
+
+
+ +
+ Title +
+
+ +
+ Transcript + +
+
+ +

+ +

+ +
+
+ + + diff --git a/edit-metatype.php b/edit-metatype.php new file mode 100644 index 0000000..67e65e6 --- /dev/null +++ b/edit-metatype.php @@ -0,0 +1,37 @@ +getRow( 'SELECT id, name FROM meta_t WHERE id=' . (int)$_GET['edit'] ) + or mtdie("Invalid metatype number!"); + +adminhead('Metatypes'); +adminmenu('manage-metatypes.php'); + +?> + +

Edit Metatype Information

+ +
+id); ?> + + + +
+ + + + + + +
Name
+ +

+ +
+ +
+ + diff --git a/edit-page.php b/edit-page.php new file mode 100644 index 0000000..617baa5 --- /dev/null +++ b/edit-page.php @@ -0,0 +1,158 @@ +url_name = $_REQUEST['page_name']; +$page = getpage($page->url_name); + +if( $_POST ) { + $page->url_name = $_POST['page_name'] ? $_POST['page_name'] : preg_replace('/\s+/', '_', strtolower(trim($_POST['title']))); + $page->status = $_POST['page_status'] == 'published' ? 'published' : 'draft'; + $page->title = $_POST['title']; + $page->body = $_POST['content']; + $page->style = $_POST['style']; + + if( USING_TIDY ) { + $tidy = new tidy; + $config = $tidy->getConfig(); + $tidy->parseString( $page->body, $config, 'UTF8' ); + $tidy->cleanRepair(); + $page->body = tidy_get_output($tidy); + } + + $action = isset($_POST['publish']) ? 'post' : 'edit'; + + if( isset( $_POST['publish'] ) ) $page->status = 'published'; // If [publish] button is used, ignore radio button + + + switch( $_POST['action'] ) { + case 'new_page': + check_nonce('new-page'); + if( !insertpage($page) ) + { + adminlog("Error inserting page.", MTS_PAGE, MTA_INSERT, E_ERROR); + mtdie('There was an error inserting the page into the database.', 'SQL Error'); + } + break; + + case 'savepage': + check_nonce('save-page-' . $page->url_name); + updatepage($page); + break; + } + if( $upload_info ) $info.=$upload_info; + if( $upload_error ) $error.=$upload_error; + if( $error ) $action='edit'; + + if( 'post' == $action ) _redirect( ADMIN_PATH . '/manage-pages.php?saved=success' ); + + $info.= '

' . ( $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'); + + +?> + + + + + +

Editing Page "title, ENT_COMPAT, 'UTF-8') ; ?>"

+ +
+url_name); ?> + + +
+ +
+
+ +
+

Page URL Name

+
+ +
+
+ +
+

Published Status

+
+ + +
+
+ + +
+
+ +
+ Title +
+
+ +
+ Page + +
+
+ + + +

+ + +

+ + + +
+ +
+ Optional CSS + + +
+ +
+ +
+ + +
+
+ + diff --git a/edit-rant.php b/edit-rant.php new file mode 100644 index 0000000..ff7fff9 --- /dev/null +++ b/edit-rant.php @@ -0,0 +1,346 @@ +id = (int)$_REQUEST['rant_id']; +$rant = getrant($rant->id); + +if( $_POST ) { + $rant->published = strtotime( $_POST['rant_date'] ); + $rant->status = $_POST['rant_status'] == 'published' ? 'published' : 'draft'; + $rant->side = $_POST['rant_side'] == 'left' ? 'left' : 'right'; + $rant->author = (int)$_POST['rant_author']; + $rant->title = $_POST['title']; + $rant->body = preg_replace('/ /', '', $_POST['content']); + $rant->link = $_POST['link']; + $rant->imagetext = $_POST['rant_imagetext']; + + if( USING_TIDY ) { + $tidy = new tidy; + $config = $tidy->getConfig(); + $tidy->parseString( $rant->body, $config, 'UTF8' ); + $tidy->cleanRepair(); + $rant->body = tidy_get_output($tidy); + } + + $action = isset($_POST['publish']) ? 'post' : 'edit'; + + if( isset( $_POST['publish'] ) ) $rant->status = 'published'; // If [publish] button is used, ignore radio button + + + $source_rantimage_filename = $_FILES['ranterImage']['tmp_name']; + extract( pre_upload_rant_image( $source_rantimage_filename ) ); + if( $upload_error ) $error.=$upload_error; + + $source_rantattachment_filename = Array(); + for($i = 0; $i < count($_FILES['rant_attachment']['error']); $i++) { + if( !is_valid_upload('rant_attachment', $i) ) { + $error .= "

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'); + +?> + + + + +

Editing Rant "title, ENT_COMPAT, 'UTF-8') ; ?>"

+ +
+id); ?> + + + +
+ +
+
+ +
+

Side

+
+
+ +
+

Author

+
+
+ +
+

Post Date

+
+
+ +
+

Published Status

+
+ + +
+
+ +status != 'draft') { ?> +
+

Broadcast Update

+
+ + +
+
+ + +
+
+ +
+ Title +
+
+ +
+ Link +
+
+ +
+ Post + +
+
+ + + +

+ + +

+ + + +
+ +
+

Image

+
+ + + + +
+ author ); + $rantimage_filename = get_rantimage_filename($rant); + if( ! file_exists( SITE_PATH_ABS.'/' . $rantimage_filename )) { + // no image in place yet + $rantimage_filename = SITE_RANT.'/' . $contributor->default_image; + if( file_exists( SITE_PATH_ABS.'/' . $rantimage_filename )) { + echo 'Currently using default rant image for this contributor. Change default.'; + } else { + $rantimage_filename = false; + echo 'There is currently no image associated with this rant,
and no default rant image associated with this contributor. +
Add a default rant image to your profile.'; + } + } else { + echo 'Custom rant image is specified.'; + } + ?> + +

Upload new rant image:
+ + +

+

Revert to default rant image.

+
+ +

+ +
+

Rant image alt text:

+ +
+

Attach files:

+
    + 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'); + foreach($attachments as $k=>$v) + printf('
  1. %s
  2. ', $v->id, SITE_HOST, SITE_PATH, get_rantattachment_filename($v->id), get_rantattachment_filename($v->id)); + ?> +
+

(Checked attachments will be deleted on submit.)

+ + (add attachment) +
+
+
+ +
+ + +
+
+ + diff --git a/edit-type.php b/edit-type.php new file mode 100644 index 0000000..a53873a --- /dev/null +++ b/edit-type.php @@ -0,0 +1,51 @@ + + +

Edit Type Information

+ +
+id); ?> + + + +
+ + + + + + + + + + + + + +
Name
Description
Metatypes + id . '" type="checkbox" name="meta[' . $s->id . ']"' . ( _objectInArrayWithIdExists( $s->id, $type->meta ) ? ' checked="checked"' : '') . '/>
'; + } + ?>
+ +

+ +
+ +
+ + diff --git a/fredart_parse.php b/fredart_parse.php new file mode 100644 index 0000000..c1b2c41 --- /dev/null +++ b/fredart_parse.php @@ -0,0 +1,58 @@ +getEntryByOffset($count); + $link = mysql_real_escape_string($entry->link); + $title = mysql_real_escape_string($entry->title); + $date = $entry->pubdate; + + $mtdb->query("INSERT INTO fredart (pubdate, title, link) + VALUES (FROM_UNIXTIME($date), '$title', '$link')", false); + } + + header('Content-Type: text/xml'); + header('Content-Length: 440'); +?> +\n"; ?> + + + + + + + flerror + + 0 + + + + message + Thanks for the ping. + + + + + + diff --git a/images/box-bg-left.gif b/images/box-bg-left.gif new file mode 100644 index 0000000000000000000000000000000000000000..c3c7e3595048ab96d25df5038059bd08d7705036 GIT binary patch literal 37 pc${eZb)cm5**9R?r(sbOGF@z{0epTQ~5)q5>o zpWXdGL8C7vb6&=(bvdv173iGbV$8u9@V@5#{{ssOJanY`Pb?{#x$EhS3oA;z)<&(r yv8L?wUyIVa8!CMCW}kntrRwgFP4_?SsPS8W`@NTW-TC*w|2H%?g~W3*SOWmheZb)cmDtX&j168KUu)E4u}A$WnebpaJ=)+ z;FRa;y%w*}z7}9yP?C~4FJqNayv4c#o%35RcX_XUU&DV_e|~|731jimB}G4(8HE_E E0l|_iOaK4? literal 0 Hc$@gn z|Ni|S73eSk0mv=}R=ovgIVqexPM%CH%kmh5_cR5aS;`>eVYI`5mvLi*iwEP`g&#bo QFfp(;FA(BbpvYhi0AXNk=>Px# literal 0 Hc$@+9d&-|z45gxOZ`~Uy{ z|NsC0|NsC0|NsC0EC2ui0EiO`000I4;3ke_X`X1Ru59bRa4gSsZQppV?|kq7z@TtQ zEE(BTJr4xw7TUm@{kM%(=7Y&!9t# z9!BK}0M1ZgW0RavE8pxhaySDAyvNP!3&AYen-@tNG7S|l1w(~0+ zo_zM{=bwNED(Iku7Ha6Bh$gD&qKr1`=%aGBH~;|*C;)&zA^@Q2rkr-_>8GHEDr%=1 zlxpg!sHUpws;su^>Z`EED(kGY)@tjmxaO+suDtf@>#x8DE9|hu7HjOW$R?}ovdlK? z?6c5DEA6z@NLFj@wb*8>?Y7)@>+QGThAZy4+ZYo#w+i<^ww+d iz4+#<@4o!@>+in+2Q2Ww1Q(3%sG0^K07oN)0027^z)Y0@ literal 0 Hc$@+9d&-|z45gxOZ`~Uy{ z000000000000000A^8LV00000EC2ui0K5PS000Hppf+q|X`X1Ru59bRa4gSsdu4+L z3_|Vyz@TtQ3>X>%0Dzz&I-k&}bcJyw2!H^iFztH3;FVxvc`yP3MS&Y`yWhcuV^L5D z1b_n~k%RmHfFv3e6%G;z2LL?*8UzRn5D^9j7nPQmn3qmn06X~Sw^#rG literal 0 Hc$@#twGzJ2@lvxqSKYyD#h0^5Dpshqu2-sBsnW1UsdB}aFJFHD{;g2D>^~7ehXDvceqmrMc9=EcSb|O)cPq0} z%n7AUi!T>0Op)mFIAJu~P^Rl_;+_TrgSIV?9D{d;cCq!krk{MW!Nx~y@y>sGH705; zYE8@?%uO0S8ci${Seo>w=r^&>U~OWW!_=g?K(k48iE0zW3Wg@#HM&iV8yK6kw`e!1 K?@)JSum%8@vc7)+ literal 0 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 aW1TO!{(NBlh4^|uWc>kI?4PWxg8mO>4_@>D literal 0 Hc$@gwv;+}!;9 z{Qdp?{{H^;_4WGt`u6tr`1ttw`T6(v_w)1fWS_z9?d>CXr^nstE`O{ygRk)L@Ku(( z>FMe3@9)dr>fhhr>+9>t-s#=l-Q(lqSD3sdgsvldsV9iABYmnRgRLQWrzeN6B6z4K zg|8xdsU(1`B6+Cv^z{4t`yh3u|Ns9000000000000000000000000000000000000 z00000A^8LV00000EC2ui0K5Ph000L6K$?)oU@R0!nR3Z&I-k&}bV{vSuh{GrsaO<; z;G+ZbXf$Hzw0g~MyWjA*d`_?1?{)J7;^=~D3o;Qh3x9@(h>41ejE#(BTJr4xiaIxY*A?5%(=7Y&!9t#9!C>oFs}7BMFbx8*W6PdRySDAy zxO3~?&AYen-@tFTruw3|@#DyoD__pMx%21Hqf4Joy*l&ZGzw_n&b_<$@8H9WA5Xr# z`Sa-0s}GM|fcy9GNG7S|l1w(~WRchfAmx-)R%zvxSZ1l^mRxq}<(FWFDQ1?~ ztzhPvXr`&=nryb|=9_TFDd(JY)@f&(*qxx~o_zM{=bwNED(Iku7Ha6Bh$gCNpqK># F06T}^*w_F7 literal 0 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 literal 0 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 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{5Sk5XTkiRKvA4X_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~;g8PbC4b;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!aP)N=bdVtZwKfl$6}qgVw=er9m|qgl&p=er=+v5+}Azd6IqsI zmfu7_F2r8m0O{`SGB2w%dlZ0WJ7PWm)72jpul?3vtqRXB@^ZT*YAofX&MCL9Zo)Bi~ghMhC`=pzZw4BHaJ6`T|Q3ZIrQ;CNCsctQsWf;wFZkzY^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<?php if( $title ) echo " — $title"; ?> + + + + + + '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; + + ?> + +

Howdy, . [Sign Out]

+ + +
    + $desc ) { + printf ( "
  • %s
  • \n", ADMIN_PATH, $url, ( $curT == $url ? ' class="current"' : '' ), htmlentities($desc) ); + if( $curT == $url && isset($submenu[$url]) ) { + $current_sub_menu = $submenu[$url]; + } + } + ?>
+ +
+ + Deleted successfully.

'; + if( $_GET['saved'] && $_GET['saved'] == 'success' ) $info.='

Changes saved successfully.

'; + + if( $error ) echo "
$error
"; + if( $info ) echo "
$info
"; + ?> +
+ +
+ + + + + + + + + + <?php echo $title?$title:'Megatokyo Administration Editor'; ?> + + + + +

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"; + foreach ( (array) $q as $a ) { + $v = substr(strstr($a, '='), 1); + $k = substr($a, 0, -(strlen($v)+1)); + $html .= "\t\t\n"; + } + $html .= "\t\t\n"; + $html .= "\t\t
\n\t\t

" . mt_explain_nonce($action) . "

\n\t\t

No  

\n\t\t
\n\t

\n"; + } else { + $html .= "\t

\n\t

" . mt_explain_nonce($action) . "

\n\t

No   Yes

\n\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

+
+ +
    + getAll('SELECT UNIX_TIMESTAMP(s.published) AS pubdate, c.name, s.message FROM scratchpad s JOIN contributor c ON s.contributor = c.id ORDER BY published DESC LIMIT 5') ); + + foreach($strips as $k=>$v) + { + $message = preg_replace('/(https?\:\/\/[^"\s\<\>]*[^.,;\'">\:\s\<\>\)\]\!])/i', + '$1', htmlentities($v->message)); + printf( '
  • %s wrote %s ago: %s
  • ', htmlspecialchars($v->name), human_time_diff($v->pubdate), $message); + } + ?> +
+

+ +

+ +

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( '
  • %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) ); +} +?> +
+ + 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.

'; +?> + + + + 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 @@ + +

Edit 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 ); + +?> + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + +
Strip #TitlePublished OnTypeBook Page #
id; ?>title; ?>published)); ?>type]; ?>book; ?> - page; ?>ViewEditDelete
+ + + + 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.='

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.

+ + + + + + + + + + + + + + > + + + + + + + +
Metatype #Name
id; ?>name); ?>EditDelete
+ +
+ + + +

Create New Metatype

+
+ + + + + +
Name
+ +

+
+
+ + \ 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 @@ + +

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 ); + +?> + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + +
Page NameTitleStatusPublished OnExcerpt
url_name ?>title; ?>status; ?>pubdate; ?>body,185); ?>url_name . '">View'; ?>EditDelete
+ + 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 @@ + +

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 ); + +?> + + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + + +
Rant #AuthorTitleStatusPublished OnExcerpt
id; ?>name; ?>title; ?>status; ?>published)); ?>body,185); ?>id . '">View'; ?>EditDelete
+ + 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 = '

Supply percentages between 0 and 100 (inclusive), please.

'; + return; + } + + // ETA must be sane + if( ($eta < $now && $percent < 100) || ($eta'; + 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'); + +?> + + +
+ + + +

Update Status Box

+
+ + + + + + + + + + + + + + + + + + + + + + + +
Current Time
Estimated Time Of Next Update
Percentage Complete
Status Description
+ +

+
+
+ + + +
+ +

Status Box History

+

Status updates that have gone before.

+ +getAll("SELECT published,eta,percentage,text FROM status ORDER BY published DESC limit 5"); + +?> + + + + + + + + + + + + + + > + + + + + + + +
PublishedETAPercentageText
published; ?>eta; ?>percentage; ?>text); ?>
+ + + 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(); + +?> + +

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.

+ +

<- Done

+ +
+ + + + + + + + + + + + + $v) { + $alternate = !$alternate; + ?> + > + + + + + + > + + + + + +
IDOrderMessage
id ?>
+

+
+ + 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.

+ +

<- Done

+ + + + + + + + + + + + + + + + > + + + + + + + + + +
ID #Twitter.com Usernameoauth_tokenoauth_token_secretAuthorized
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
+ + +
+ + + +

Add Twitter User

+ +

+ +
+ + 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.='

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.

+ + + + + + + + + + + + + + + + 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 #NameDescriptionStripsMetatypes
id; ?>name); ?>description); ?>strips ?> EditDelete
+ + +
+ + + +

Create New Type

+
+ + + + + + + + + +
Name
Description
+ +

+
+
+ + 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"; + } +} + +?> + +

Post New Comic

+ +
+ + +
+ +
+
+ +
+

Comic Type

+
+
+ +
+

Post Date

+
+
+ +
+

Strip Number

+
+
+ +
+

Upload Comic

+
+ + +
+
+ +
+

Book Page Number

+
-
+
+ +
+
+ +
+ Title +
+
+ +
+ Transcript + +
+
+ +

+ +

+ +
+
+ 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

+ +
+ + + +
+ +
+
+ +
+

Page URL Name

+
+ +
+
+ +
+

Published Status

+
+ + +
+
+ +
+
+ +
+ Title +
+
+ +
+ Page + + +
+
+ + +

+ + +

+ + + +
+ +
+ Optional CSS + + +
+
+ +
+ + +
+ +
+ + 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

+ +
+ + + + +
+ +
+
+ +
+

Side

+
+
+ +
+

Author

+
+
+ +
+

Post Date

+
+
+ +
+

Published Status

+
+ + +
+
+ +
+
+ +
+ Title +
+
+ +
+ Link +
+
+ +
+ Post + + +
+ + + BasePath = 'include/fckeditor/'; + $oFCKeditor->Value = ''; + $oFCKeditor->Create(); + */ + ?> + + + +
+ + + +

+ + +

+ + + +
+ +
+

Image

+
+ + + + +
+

default_image; + if( file_exists( SITE_PATH_ABS.'/' . $rantimage_filename )) { + echo 'Currently using default rant image for this contributor. Change default.'; + } else { + echo $rantimage_filename; + $rantimage_filename = false; + echo 'There is currently no image associated with this rant,
and no default rant image associated with this contributor. +
Add a default rant image to your profile.'; + } + ?> +

+

Upload a rant image:
+ + +

+ +
+ +

+ +
+

Rant image alt text:

+ +
+

Attach files:

+
    +
+ + (add attachment) +
+
+
+ +
+ + +
+ +
+ + 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

+
+ + + + + + +

Post As: + Edit +

+

Message Preset: + Edit +

+

+

Characters remaining: 140 +

+ Post to: + + +

+
+ + + + + */ ?> + +
+ + +$v) { ?> + + +
+ +
+ + 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"; +?> + + + Megatokyo Admin Log + http://www.megatokyo.com/admin + Listing of administrative events: errors, warnings, and notifications. + en-us + + $v) { ?> + + <?php echo htmlentities($v->message, ENT_COMPAT, 'UTF-8') ?> + cmail, $v->cname) ?> + logdate ) ?> + logdate) ?> + + level ?> + section ?> + action ?> + message, ENT_COMPAT, 'UTF-8') ?> + + + + 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 Scratchpad + http://www.megatokyo.com/admin + Listing of administrative events: errors, warnings, and notifications. + en-us + + $v) { ?> + + <?php echo $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/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 Log + http://www.megatokyo.com/admin + Listing of administrative events: errors, warnings, and notifications. + en-us + + $v) { ?> + + <?php echo htmlentities($v->message, ENT_COMPAT, 'UTF-8') ?> + cmail, $v->cname) ?> + logdate ) ?> + logdate) ?> + action ?> + 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'); + +?> + +

Swap Comics

+ +
+ +
+ + + + + + + + +
First Comic +
Second Comic +
+ +

+ +
+ +
+ + 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 ) { + +?> +
+ +

Editing "name); ?>"

+

Modify details for this contributer.

+ +
+Name +

+ +

+ +

+ +
+ +
+Rant Defaults +

+ +

+ +default_image; +if( !file_exists( SITE_PATH_ABS.'/' . $rantimage_filename )) { + echo '

There is currently no default rant image for this contributor.

'; +} else { + echo '

'; +} +?> + +
+ +
+Change Password +

+ +

+ +

+
+ + +
+ +

+ +
+
+ + diff --git a/users.php b/users.php new file mode 100644 index 0000000..e9f2a32 --- /dev/null +++ b/users.php @@ -0,0 +1,70 @@ + +

User Administration

+

Make changes to accounts for contributers to the website.

+ +getAll("SELECT id,name,email,nameplate FROM contributor"); + +?> + + + + + + + + + + + + + + + > + + + + + + + + +
User #UsernameNicknameEmail
id; ?>name; ?>nameplate; ?>email; ?>Edit
+ + +
+ + +

Create New Contributor

+
+ + + + +
Username +
+ +

+
+
+ + 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 @@ + +

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 ); + +?> + + + + + + + + + + + + + + + + > + + + + + + + + + +
DateContributorSectionActionLevelMessage
logdate; ?>cname; ?>section; ?>action; ?>level ?>message; ?>
+ + + + 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