1.7 Developer Pre-Release

This commit is contained in:
Jared Hancock
2012-03-19 17:49:16 -05:00
parent fa72b41bfa
commit 2bdbafec68
416 changed files with 40783 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.htaccess
php53.cgi
include/ost-config.php
*.sw[a-z]

280
LICENSE.txt Normal file
View File

@@ -0,0 +1,280 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
osTicket
========
osTicket is a widely-used open source support ticket system. It seamlessly
integrates inquiries created via email, phone and web-based forms into a
simple easy-to-use multi-user web interface. Manage, organize and archive
all your support requests and responses in one place while providing your
customers with accountability and responsiveness they deserve.
How osTicket works for you
--------------------------
1. Users create tickets via your website, email, or phone
1. Incoming tickets are saved and assigned to agents
1. Agents help your users resolve their issues
osTicket is an attractive alternative to higher-cost and complex customer
support systems; simple, lightweight, reliable, open source, web-based and
easy to setup and use. The best part is, it's completely free.
Installation
------------
Download the
[source](./zipball/master) from
the GitHub page. Follow the instructions for installation on the osTicket
[wiki](http://osticket.com/wiki/Installation).
Help
----
Visit the [wiki](http://osticket.com/wiki/Home) or the
[forum](http://osticket.com/forums/). And if you'd like professional help
managing your osTicket installation,
[commercial support](http://osticket.com/support/) is available.
Contributing
------------
Create your own fork of the project and use
[git-flow](https://github.com/nvie/gitflow) to create a new feature. Once
the feature is published in your fork, send a pull request to begin the
conversation of integrating your new feature into osTicket.

85
api/api.inc.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
/*********************************************************************
api.inc.php
File included on every API page...handles security and abuse issues
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
//postfix exit codes see /usr/include/sysexits.h
define('EX_DATAERR', 65); /* data format error */
define('EX_NOINPUT', 66); /* cannot open input */
define('EX_UNAVAILABLE', 69); /* service unavailable */
define('EX_IOERR', 74); /* input/output error */
define('EX_TEMPFAIL',75); /* temp failure; user is invited to retry */
define('EX_NOPERM', 77); /* permission denied */
define('EX_CONFIG', 78); /* configuration error */
define('EX_SUCCESS',0); /* success baby */
if(!file_exists('../main.inc.php')) exit(EX_CONFIG);
require_once('../main.inc.php');
if(!defined('INCLUDE_DIR')) exit(EX_CONFIG);
require_once(INCLUDE_DIR.'class.http.php');
require_once(INCLUDE_DIR.'class.api.php');
define('OSTAPIINC',TRUE); // Define tag that included files can check
$remotehost=(isset($_SERVER['HTTP_HOST']) || isset($_SERVER['REMOTE_ADDR']))?TRUE:FALSE;
/* API exit helper */
function api_exit($code,$msg='') {
global $remotehost,$cfg;
if($code!=EX_SUCCESS) {
//Error occured...
$_SESSION['api']['errors']+=1;
$_SESSION['api']['time']=time();
Sys::log(LOG_WARNING,"API error - code #$code",$msg);
//echo "API Error:.$msg";
}
if($remotehost){
switch($code) {
case EX_SUCCESS:
Http::response(200,$code,'text/plain');
break;
case EX_UNAVAILABLE:
Http::response(405,$code,'text/plain');
break;
case EX_NOPERM:
Http::response(403,$code,'text/plain');
break;
case EX_DATAERR:
case EX_NOINPUT:
default:
Http::response(416,$code,'text/plain');
}
}
exit($code);
}
//Remote hosts need authorization.
if($remotehost) {
$ip=$_SERVER['REMOTE_ADDR'];
$key=$_SERVER['HTTP_USER_AGENT']; //pulling all tricks.
//Upto 10 consecutive errors allowed...before a 5 minute timeout.
//One more error during timeout and timeout starts a new clock
if($_SESSION['api']['errors']>10 && (time()-$_SESSION['api']['time'])<=5*60) { // timeout!
api_exit(EX_NOPERM,"Remote host [$ip] in timeout - error #".$_SESSION['api']['errors']);
}
//Check API key & ip
if(!Validator::is_ip($ip) || !Api::validate($key,$ip)) {
api_exit(EX_NOPERM,'Unknown remote host ['.$ip.'] or invalid API key ['.$key.']');
}
//At this point we know the remote host/IP is allowed.
$_SESSION['api']['errors']=0; //clear errors for the session.
}
?>

21
api/cron.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
/*********************************************************************
cron.php
File to handle cron job calls (local and remote).
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
@chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
require('api.inc.php');
require_once(INCLUDE_DIR.'class.cron.php');
Cron::run();
Sys::log(LOG_DEBUG,'Cron Job','External cron job executed ['.$_SERVER['REMOTE_ADDR'].']');
?>

27
api/http.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
/*********************************************************************
api.php
Controller for the osTicket API
Jared Hancock
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
chdir('..');
include "main.inc.php";
# Include the main api urls
require_once INCLUDE_DIR."class.dispatcher.php";
$dispatcher = Dispatcher::include_urls("urls.conf.php");
# Call the respective function
$dispatcher->resolve($_SERVER['PATH_INFO']);
?>

3
api/index.php Normal file
View File

@@ -0,0 +1,3 @@
<?php
header('Location: ../');
?>

132
api/pipe.php Normal file
View File

@@ -0,0 +1,132 @@
#!/usr/bin/php -q
<?php
/*********************************************************************
pipe.php
Converts piped emails to ticket. Both local and remote!
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
@chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
ini_set('memory_limit', '256M'); //The concern here is having enough mem for emails with attachments.
require('api.inc.php');
require_once(INCLUDE_DIR.'class.mailparse.php');
require_once(INCLUDE_DIR.'class.email.php');
//Make sure piping is enabled!
if(!$cfg->enableEmailPiping())
api_exit(EX_UNAVAILABLE,'Email piping not enabled - check MTA settings.');
//Get the input
$data=isset($_SERVER['HTTP_HOST'])?file_get_contents('php://input'):file_get_contents('php://stdin');
if(empty($data)){
api_exit(EX_NOINPUT,'No data');
}
//Parse the email.
$parser= new Mail_Parse($data);
if(!$parser->decode()){ //Decode...returns false on decoding errors
api_exit(EX_DATAERR,'Email parse failed ['.$parser->getError()."]\n\n".$data);
}
//Check from address. make sure it is not a banned address.
$fromlist = $parser->getFromAddressList();
//Check for parsing errors on FROM address.
if(!$fromlist || PEAR::isError($fromlist)){
api_exit(EX_DATAERR,'Invalid FROM address ['.$fromlist?$fromlist->getMessage():''."]\n\n".$data);
}
$from=$fromlist[0]; //Default.
foreach($fromlist as $fromobj){
if(!Validator::is_email($fromobj->mailbox.'@'.$fromobj->host))
continue;
$from=$fromobj;
break;
}
//TO Address:Try to figure out the email associated with the message.
$tolist = $parser->getToAddressList();
foreach ($tolist as $toaddr){
if(($emailId=Email::getIdByEmail($toaddr->mailbox.'@'.$toaddr->host))){
//We've found target email.
break;
}
}
if(!$emailId && ($cclist=$parser->getCcAddressList())) {
foreach ($cclist as $ccaddr){
if(($emailId=Email::getIdByEmail($ccaddr->mailbox.'@'.$ccaddr->host))){
break;
}
}
}
//TODO: Options to reject emails without a matching To address in db? May be it was Bcc? Current Policy: If you pipe, we accept policy
require_once(INCLUDE_DIR.'class.ticket.php'); //We now need this bad boy!
$var=array();
$deptId=0;
$name=trim($from->personal,'"');
if($from->comment && $from->comment[0])
$name.=' ('.$from->comment[0].')';
$subj=utf8_encode($parser->getSubject());
if(!($body=Format::stripEmptyLines($parser->getBody())) && $subj)
$body=$subj;
$var['mid']=$parser->getMessageId();
$var['email']=$from->mailbox.'@'.$from->host;
$var['name']=$name?utf8_encode($name):$var['email'];
$var['emailId']=$emailId?$emailId:$cfg->getDefaultEmailId();
$var['subject']=$subj?$subj:'[No Subject]';
$var['message']=utf8_encode(Format::stripEmptyLines($body));
$var['header']=$parser->getHeader();
$var['pri']=$cfg->useEmailPriority()?$parser->getPriority():0;
$ticket=null;
if(preg_match ("[[#][0-9]{1,10}]",$var['subject'],$regs)) {
$extid=trim(preg_replace("/[^0-9]/", "", $regs[0]));
$ticket= new Ticket(Ticket::getIdByExtId($extid));
//Allow mismatched emails?? For now hell NO.
if(!is_object($ticket) || strcasecmp($ticket->getEmail(),$var['email']))
$ticket=null;
}
$errors=array();
$msgid=0;
if(!$ticket){ //New tickets...
# Apply filters against the new ticket
$ef = new EmailFilter($var); $ef->apply($var);
$ticket=Ticket::create($var,$errors,'email');
if(!is_object($ticket) || $errors){
api_exit(EX_DATAERR,'Ticket create Failed '.implode("\n",$errors)."\n\n");
}
$msgid=$ticket->getLastMsgId();
}else{
$message=$var['message'];
//Strip quoted reply...TODO: figure out how mail clients do it without special tag..
if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()) && strpos($var['message'],$tag))
list($message)=split($tag,$var['message']);
//post message....postMessage does the cleanup.
if(!($msgid=$ticket->postMessage($message,'Email',$var['mid'],$var['header']))) {
api_exit(EX_DATAERR,"Unable to post message \n\n $message\n");
}
}
//Ticket created...save attachments if enabled.
if($cfg->allowEmailAttachments()) {
if($attachments=$parser->getAttachments()){
//print_r($attachments);
foreach($attachments as $k=>$attachment){
if($attachment['filename'] && $cfg->canUploadFileType($attachment['filename'])) {
$ticket->saveAttachment($attachment['filename'],$attachment['body'],$msgid,'M');
}
}
}
}
api_exit(EX_SUCCESS);
?>

11
api/urls.conf.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
# What about patterns("api/ticket.php:TicketController", ...) since if the
# class is given as the prefix, it isn't defined in more than one file. This
# would allow for speficying imports only if an item is defined in a
# different class (with the array("class", "method") syntax)
return patterns("api.ticket.php:TicketController",
url_post("^/tickets\.(?P<format>xml|json)$", "create")
);
?>

View File

@@ -0,0 +1,29 @@
#header, #nav, #meta, #footer, #reply, #pagination, .reload, .refresh, form, .thread, hr, #kbAttachments, .back {
display: none;
}
th {
text-align: left;
}
a {
color: #000;
text-decoration: none;
}
caption {
text-align: left;
padding-bottom: 10px;
font-weight: bold;
}
.message, .response {
border-bottom: 1px solid #000;
margin-bottom: 20px;
padding-bottom: 10px;
}
.message th, .response th {
font-size: 12pt;
font-weight: bold;
padding-bottom: 5px;
}

View File

@@ -0,0 +1,712 @@
/* Based on Normalize.css - with tags we won't use removed. */
html {
font-size: 100%;
overflow-y: scroll;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
margin: 0;
font-size: 13px;
line-height: 1.231;
padding: 0;
}
body, input, select, textarea {
font-family: sans-serif;
color: #000;
}
b, strong {
font-weight: bold;
}
blockquote {
margin: 1em 40px;
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
small {
font-size: 85%;
}
ul, ol {
margin: 1em 0;
padding: 0 0 0 30px;
}
img {
border: 0;
vertical-align: middle;
}
form {
margin: 0;
}
fieldset {
border: 0;
margin: 0;
padding: 0;
}
label {
cursor: pointer;
}
input, select, textarea {
font-size: 100%;
margin: 0;
vertical-align: baseline;
*vertical-align: middle;
}
input {
line-height: normal;
*overflow: visible;
}
table input {
*overflow: auto;
}
input[type="button"], input[type="reset"], input[type="submit"] {
cursor: pointer;
-webkit-appearance: button;
}
input[type="checkbox"], input[type="radio"] {
box-sizing: border-box;
}
input[type="search"] {
-webkit-appearance: textfield;
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
textarea {
overflow: auto;
vertical-align: top;
resize: vertical;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
td {
vertical-align: top;
}
h1, h2, h3, h4, h5, h6, form, fieldset {
margin: 0;
padding: 0;
}
/* Typography */
a {
color: #0072bc;
text-decoration: none;
}
h1 {
color: #00AEEF;
font-weight: normal;
font-size: 20px;
}
h3 {
font-size: 16px;
}
h2 {
font-size: 16px;
color: #999;
}
/* Helpers */
.centered { text-align: center;}
.clear { clear:both; height: 1px; visibility: none;}
.hidden { display: none;}
.faded { color:#666;}
/* Pagination */
#pagination {
border: 0;
margin: 0 0 40px 0;
padding: 0;
}
#pagination li {
border: 0;
margin: 0;
padding: 0;
font-size: 11px;
list-style: none;
display: inline;
}
#pagination a {
margin-right: 2px;
display: block;
float: left;
padding: 3px 6px;
text-decoration: none;
}
#pagination a:hover {
color: #ff0084;
}
#pagination .previousOff, #pagination .nextOff {
color: #666;
display: block;
float: left;
font-weight: bold;
padding: 3px 4px;
}
#pagination .next a, #pagination .previous a {
font-weight: bold;
}
#pagination .active {
color: #000;
font-weight: bold;
margin-right: 2px;
display: block;
float: left;
padding: 3px 6px;
text-decoration: none;
}
/* Alerts & Notices */
#msg_notice { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #0a0; background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; }
#msg_warning { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #f26522; background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; }
#msg_error { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #a00; background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; }
.warning {
background: #ffc;
font-style: italic;
}
.warning strong {
text-transform: uppercase;
color: #a00;
font-style: normal;
}
.error {
color:#f00;
}
.error input {
border:1px solid #f00;}
/* Main layout */
body {
background: url('../images/page_bg.png') top left repeat-x #c8c8c8;
}
#container {
background: #fff;
width: 840px;
margin: 0 auto;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
-webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
}
#header {
position: relative;
height: 71px;
padding: 0 20px;
}
#header #logo {
width: 220px;
height: 71px;
float: left;
}
#header p {
width: 400px;
text-align: right;
margin: 0;
padding: 10px 0;
float: right;
}
#nav {
margin: 0 20px;
padding: 2px 10px;
height: 20px;
background: url('../images/nav_bg.png') top left repeat-x;
border-top: 1px solid #aaa;
}
#nav li {
margin: 0;
padding: 0;
list-style: none;
display: inline;
}
#nav li a {
display: block;
width: auto;
float: left;
height: 20px;
line-height: 20px;
text-align: center;
padding: 0 10px 0 32px;
margin-left: 10px;
color: #333;
border-radius: 20px;
-webkit-border-radius: 20px;
-moz-border-radius: 20px;
background-position: 10px 50%;
background-repeat: no-repeat;
}
#nav li a.active, #nav li a:hover {
background-color: #dbefff;
color: #000;
}
#nav li a:hover {
background-color: #ededed;
color: #0054a6;
}
#nav li a.home {
background-image: url('../images/icons/home.png');
}
#nav li a.kb {
background-image: url('../images/icons/kb.png');
}
#nav li a.new {
background-image: url('../images/icons/new.png');
}
#nav li a.status {
background-image: url('../images/icons/status.png');
}
#nav li a.tickets {
background-image: url('../images/icons/tix.png');
}
#content {
padding: 20px 0;
margin: 0 20px;
background: url('../images/content_bg.png') top left repeat-x;
}
#cnbg {
padding: 5px 0;
margin: 0 20px;
height: auto !important;
height: 350px;
min-height: 350px;
background: none;
}
#footer {
text-align: center;
font-size: 11px;
color: #333;
}
#footer a {
color: #333;
}
#footer p {
margin: 10px 0 0 0;
}
#footer #poweredBy {
display: block;
width: 126px;
height: 23px;
outline: none;
text-indent: -9999px;
margin: 0 auto;
background: url('../images/poweredby.png?1319571688') top left no-repeat;
}
/* Landing page */
#landing_page #new_ticket {
margin-top: 40px;
width: 295px;
padding-left: 75px;
float: left;
background: url('../images/new_ticket_icon.png?1319577021') top left no-repeat;
}
#landing_page #new_ticket input[type=submit] {
background: url('../images/open_ticket_btn.png?1319566422') top left no-repeat;
}
#landing_page #check_status {
margin-top: 40px;
width: 295px;
padding-left: 75px;
float: right;
background: url('../images/check_status_icon.png?1319577072') top left no-repeat;
}
#landing_page #check_status input[type=submit] {
background: url('../images/check_status_btn.png?1319571066') top left no-repeat;
}
#landing_page form div {
margin-bottom: 20px;
height: 60px;
min-height: 60px;
}
#landing_page form input[type=submit] {
display: block;
width: 192px;
height: 38px;
border: none;
margin: 0;
padding: 0;
text-indent: -9999px;
}
#faq {
clear: both;
margin: 0;
padding: 5px;
}
#faq ol {
font-size: 15px;
margin-left: 0;
padding-left: 0;
}
#faq ol li {
list-style: none;
margin: 0 0 10px 0;
color: #999;
}
#faq ol li a {
display: block;
height: 16px;
line-height: 16px;
padding-left: 24px;
background: url('../images/icons/page.png?1319579499') 0 50% no-repeat;
}
/* Knowledgebase */
#kb {
margin: 2px 0;
padding: 5px;
overflow: hidden;
}
#kb > li {
margin: 0 0 5px 0;
padding: 10px;
width: auto;
float: left;
clear: both;
list-style: none;
}
#kb > li h4 {
padding-bottom:3px;
margin-bottom:3px;
}
#kb > li h4 span {
color:#666;
}
#kb > li h4 a {
font-size: 14px;
padding-left: 24px;
background: url('../images/icons/page.png?1319579499') 0 50% no-repeat;
}
#breadcrumbs {
color: #333;
margin-bottom: 15px;
}
#breadcrumbs a {
color: #555;
}
/* New Ticket & Log In Forms */
#ticketForm div, #clientLogin div {
clear: both;
padding: 3px 0;
overflow: hidden;
}
#ticketForm div label, #clientLogin div label {
display: block;
width: 140px;
float: left;
}
#ticketForm div input, #ticketForm div textarea, #clientLogin div input, #clientLogin div textarea {
width: auto;
border: 1px solid #aaa;
background: #fff;
margin-right: 10px;
display: block;
float: left;
}
#ticketForm div select, #clientLogin div select {
display: block;
float: left;
}
#ticketForm div textarea, #clientLogin div textarea {
width: 600px;
}
#ticketForm div em, #clientLogin div em {
color: #777;
}
#ticketForm div .captcha, #clientLogin div .captcha {
width: 88px;
height: 31px;
background: #000;
display: block;
float: left;
margin-right: 20px;
}
#ticketForm div label.inline, #clientLogin div label.inline {
width: auto;
padding: 0 10px;
}
#ticketForm div label.required, #clientLogin div label.required {
font-weight: bold;
}
#ticketForm div.captchaRow, #clientLogin div.captchaRow {
line-height: 31px;
}
#ticketForm div.captchaRow input, #clientLogin div.captchaRow input {
position: relative;
top: 6px;
}
#ticketForm div.error input, #clientLogin div.error input {
border: 1px solid #a00;
}
#ticketForm div.error label, #clientLogin div.error label {
color: #a00;
}
#ticketForm p, #clientLogin p {
clear: both;
text-align: center;
}
#clientLogin {
width: 400px;
margin-top: 20px;
padding: 10px 100px 10px 10px;
border: 1px solid #ccc;
background: url('../images/lock.png?1319655200') 440px 50% no-repeat #f6f6f6;
}
#clientLogin strong {
font-size: 11px;
color: #d00;
display: block;
padding-left: 140px;
}
#clientLogin #email {
width: 250px;
margin-right: 0;
}
#clientLogin #ticketno {
width: 120px;
margin-right: 0;
}
/* Ticket List */
.Icon {
width: auto;
padding-left: 20px;
background-position: left center;
background-repeat: no-repeat;
color: #006699;
text-decoration: none;
}
a.Icon:hover {
text-decoration: underline;
}
.Icon.Ticket {
background: url('../images/icons/ticket.gif?1319654018') 0 0 no-repeat;
}
.Icon.webTicket {
background: url('../images/icons/ticket_source_web.gif?1319654283') 0 0 no-repeat;
}
.Icon.emailTicket {
background: url('../images/icons/ticket_source_email.gif?1319654484') 0 0 no-repeat;
}
.Icon.phoneTicket {
background: url('../images/icons/ticket_source_phone.gif?1319654401') 0 0 no-repeat;
}
.Icon.otherTicket {
background: url('../images/icons/ticket_source_other.gif?1319654433') 0 0 no-repeat;
}
.Icon.attachment {
background-image: url('../images/icons/attachment.gif?1319556657');
}
.Icon.file {
background-image: url('../images/icons/attachment.gif?1319556657');
}
.Icon.refresh {
background-image: url('../images/icons/refresh.gif?1319556657');
}
.Icon.thread {
font-weight: bold;
font-size: 1em;
background-image: url('../images/icons/thread.gif?1319556657');
}
#ticketTable {
border: 1px solid #aaa;
border-left: none;
border-bottom: none;
}
#ticketTable caption {
padding: 5px;
text-align: left;
color: #000;
background: #ddd;
border: 1px solid #aaa;
border-bottom: none;
font-weight: bold;
}
#ticketTable th {
height: 24px;
line-height: 24px;
background: #e1f2ff;
border: 1px solid #aaa;
border-right: none;
border-top: none;
}
#ticketTable th a {
color: #000;
}
#ticketTable td {
padding: 2px;
border: 1px solid #aaa;
border-right: none;
border-top: none;
}
#ticketTable tr.alt td {
background: #f9f9f9;
}
#ticketSearchForm {
display: inline-block;
float: left;
padding: 0 0 5px 0;
}
a.refresh {
display: block;
width: auto;
float: right;
height: 20px;
line-height: 20px;
text-align: center;
padding: 0 10px 0 28px;
border: 1px solid #aaa;
margin-left: 10px;
color: #333;
background-position: 5px 50%;
background-repeat: no-repeat;
background-image: url('../images/icons/refresh.png?1319653435');
}
.infoTable {
background: #F4FAFF;
}
.infoTable th {
text-align: left;
}
#ticketThread table {
margin-top: 10px;
border: 1px solid #aaa;
border-bottom: 2px solid #aaa;
}
#ticketThread table th {
text-align: left;
border-bottom: 1px solid #aaa;
font-size: 11pt;
padding: 5px;
}
#ticketThread table td {
padding: 5px;
}
#ticketThread .message th {
background: #d8efff;
}
#ticketThread .response th {
background: #ddd;
}
#ticketThread .info {
padding: 5px;
background: #f9f9f9;
border-top: 1px solid #ddd;
height: 16px;
line-height: 16px;
}
#ticketThread .info a {
display: inline-block;
margin: 5px 20px 5px 0;
padding-left: 24px;
height: 16px;
line-height: 16px;
background-position: 0 50%;
background-repeat: no-repeat;
}
#ticketThread .info .pdf {
background-image: url('../images/filetypes/pdf.png?1319636863');
}
#reply {
margin-top: 20px;
padding: 10px 5px;
background: #f9f9f9;
border: 1px solid #ccc;
}
#reply h2 {
margin-bottom: 10px;
}
#reply table {
width: 800px;
}
#reply table td {
vertical-align: top;
}
#reply textarea {
width: 628px !important;
}
#reply input[type=text], #reply #response_options textarea {
border: 1px solid #aaa;
background: #fff;
}
#reply .attachments .uploads div {
display: inline-block;
padding-right: 20px;
}
#reply .file {
display: inline-block;
padding-left: 20px;
margin-right: 20px;
background: url('../images/icons/file.gif') 0 50% no-repeat;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

65
client.inc.php Normal file
View File

@@ -0,0 +1,65 @@
<?php
/*********************************************************************
client.inc.php
File included on every client page
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']),basename(__FILE__))) die('kwaheri rafiki!');
$thisdir=str_replace('\\\\', '/', realpath(dirname(__FILE__))).'/';
if(!file_exists($thisdir.'main.inc.php')) die('Fatal Error.');
require_once($thisdir.'main.inc.php');
if(!defined('INCLUDE_DIR')) die('Fatal error');
/*Some more include defines specific to client only */
define('CLIENTINC_DIR',INCLUDE_DIR.'client/');
define('OSTCLIENTINC',TRUE);
define('ASSETS_PATH',ROOT_PATH.'assets/default/');
//Check the status of the HelpDesk.
if(!is_object($cfg) || !$cfg->getId() || $cfg->isHelpDeskOffline()) {
include('./offline.php');
exit;
}
//Forced upgrade? Version mismatch.
if(defined('THIS_VERSION') && strcasecmp($cfg->getVersion(),THIS_VERSION)) {
die('System is offline for an upgrade.');
exit;
}
/* include what is needed on client stuff */
require_once(INCLUDE_DIR.'class.client.php');
require_once(INCLUDE_DIR.'class.ticket.php');
require_once(INCLUDE_DIR.'class.dept.php');
//clear some vars
$errors=array();
$msg='';
$thisclient=null;
//Make sure the user is valid..before doing anything else.
if($_SESSION['_client']['userID'] && $_SESSION['_client']['key'])
$thisclient = new ClientSession($_SESSION['_client']['userID'],$_SESSION['_client']['key']);
//is the user logged in?
if($thisclient && $thisclient->getId() && $thisclient->isValid()){
$thisclient->refreshSession();
}
$nav = new UserNav($thisclient, 'home');
?>

BIN
images/bg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 B

BIN
images/cal.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

BIN
images/captcha/bubbles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
images/captcha/crackle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
images/captcha/grass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
images/captcha/lines.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
images/captcha/ripple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

BIN
images/captcha/sand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
images/captcha/silk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
images/fibres.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
images/home.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

BIN
images/icons/attachment.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

BIN
images/icons/refresh.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

BIN
images/icons/thread.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

BIN
images/icons/ticket.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

BIN
images/lipsum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
images/logo2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/logout.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

BIN
images/my_tickets.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

BIN
images/new_ticket.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

BIN
images/new_ticket_icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
images/poweredby.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
images/rainbow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/refresh_btn.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

BIN
images/ticket_status.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
images/verticalbar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
images/view_closed_btn.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

BIN
images/view_open_btn.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

805
include/JSON.php Normal file
View File

@@ -0,0 +1,805 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Converts to and from JSON format.
*
* JSON (JavaScript Object Notation) is a lightweight data-interchange
* format. It is easy for humans to read and write. It is easy for machines
* to parse and generate. It is based on a subset of the JavaScript
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
* This feature can also be found in Python. JSON is a text format that is
* completely language independent but uses conventions that are familiar
* to programmers of the C-family of languages, including C, C++, C#, Java,
* JavaScript, Perl, TCL, and many others. These properties make JSON an
* ideal data-interchange language.
*
* This package provides a simple encoder and decoder for JSON notation. It
* is intended for use with client-side Javascript applications that make
* use of HTTPRequest to perform server communication functions - data can
* be encoded into JSON notation for use in a client-side javascript, or
* decoded from incoming Javascript requests. JSON format is native to
* Javascript, and can be directly eval()'ed with no further parsing
* overhead
*
* All strings should be in ASCII or UTF-8 format!
*
* LICENSE: Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met: Redistributions of source code must retain the
* above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* @category
* @package Services_JSON
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @copyright 2005 Michal Migurski
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_SLICE', 1);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_STR', 2);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_ARR', 3);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_OBJ', 4);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_CMT', 5);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_LOOSE_TYPE', 16);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
/**
* Converts to and from JSON format.
*
* Brief example of use:
*
* <code>
* // create a new instance of Services_JSON
* $json = new Services_JSON();
*
* // convert a complexe value to JSON notation, and send it to the browser
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
* $output = $json->encode($value);
*
* print($output);
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
*
* // accept incoming POST data, assumed to be in JSON notation
* $input = file_get_contents('php://input', 1000000);
* $value = $json->decode($input);
* </code>
*/
class Services_JSON
{
/**
* constructs a new JSON instance
*
* @param int $use object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
* "{...}" syntax creates associative arrays
* instead of objects in decode().
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
* Values which can't be encoded (e.g. resources)
* appear as NULL instead of throwing errors.
* By default, a deeply-nested resource will
* bubble up with an error, so all return values
* from encode() should be checked with isError()
*/
function Services_JSON($use = 0)
{
$this->use = $use;
}
/**
* convert a string from one UTF-16 char to one UTF-8 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf16 UTF-16 character
* @return string UTF-8 character
* @access private
*/
function utf162utf8($utf16)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
switch(true) {
case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F))
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* convert a string from one UTF-8 char to one UTF-16 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf8 UTF-8 character
* @return string UTF-16 character
* @access private
*/
function utf82utf16($utf8)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
}
switch(strlen($utf8)) {
case 1:
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8;
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8{0}) >> 2))
. chr((0xC0 & (ord($utf8{0}) << 6))
| (0x3F & ord($utf8{1})));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8{0}) << 4))
| (0x0F & (ord($utf8{1}) >> 2)))
. chr((0xC0 & (ord($utf8{1}) << 6))
| (0x7F & ord($utf8{2})));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* encodes an arbitrary variable into JSON format
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function encode($var)
{
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false';
case 'NULL':
return 'null';
case 'integer':
return (int) $var;
case 'double':
case 'float':
return (float) $var;
case 'string':
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = '';
$strlen_var = strlen($var);
/*
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var{$c});
switch (true) {
case $ord_var_c == 0x08:
$ascii .= '\b';
break;
case $ord_var_c == 0x09:
$ascii .= '\t';
break;
case $ord_var_c == 0x0A:
$ascii .= '\n';
break;
case $ord_var_c == 0x0C:
$ascii .= '\f';
break;
case $ord_var_c == 0x0D:
$ascii .= '\r';
break;
case $ord_var_c == 0x22:
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\'.$var{$c};
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var{$c};
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, ord($var{$c + 1}));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF0) == 0xE0):
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF8) == 0xF0):
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}));
$c += 3;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}),
ord($var{$c + 4}));
$c += 4;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFE) == 0xFC):
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}),
ord($var{$c + 4}),
ord($var{$c + 5}));
$c += 5;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return '"'.$ascii.'"';
case 'array':
/*
* As per JSON spec if any array key is not an integer
* we must treat the the whole array as an object. We
* also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines
* will create an array with empty indexes up to
* max_index which can cause memory issues and because
* the keys, which may be relevant, will be remapped
* otherwise.
*
* As per the ECMA and JSON specification an object may
* have any string as a property. Unfortunately due to
* a hole in the ECMA specification if the key is a
* ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's
* bracket notation.
*/
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
$properties = array_map(array($this, 'name_value'),
array_keys($var),
array_values($var));
foreach($properties as $property) {
if(Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
}
// treat it like a regular array
$elements = array_map(array($this, 'encode'), $var);
foreach($elements as $element) {
if(Services_JSON::isError($element)) {
return $element;
}
}
return '[' . join(',', $elements) . ']';
case 'object':
$vars = get_object_vars($var);
$properties = array_map(array($this, 'name_value'),
array_keys($vars),
array_values($vars));
foreach($properties as $property) {
if(Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
default:
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
? 'null'
: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
}
}
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
* @param string $name name of key to use
* @param mixed $value reference to an array element to be encoded
*
* @return string JSON-formatted name-value pair, like '"name":value'
* @access private
*/
function name_value($name, $value)
{
$encoded_value = $this->encode($value);
if(Services_JSON::isError($encoded_value)) {
return $encoded_value;
}
return $this->encode(strval($name)) . ':' . $encoded_value;
}
/**
* reduce a string by removing leading and trailing comments and whitespace
*
* @param $str string string value to strip of comments and whitespace
*
* @return string string value stripped of comments and whitespace
* @access private
*/
function reduce_string($str)
{
$str = preg_replace(array(
// eliminate single line comments in '// ...' form
'#^\s*//(.+)$#m',
// eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
), '', $str);
// eliminate extraneous space
return trim($str);
}
/**
* decodes a JSON string into appropriate variable
*
* @param string $str JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
* See argument 1 to Services_JSON() above for object-output behavior.
* Note that decode() always returns strings
* in ASCII or UTF-8 format!
* @access public
*/
function decode($str)
{
$str = $this->reduce_string($str);
switch (strtolower($str)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
$m = array();
if (is_numeric($str)) {
// Lookie-loo, it's a number
// This would work on its own, but I'm trying to be
// good about returning integers where appropriate:
// return (float)$str;
// Return float or int, as appropriate
return ((float)$str == (integer)$str)
? (integer)$str
: (float)$str;
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT
$delim = substr($str, 0, 1);
$chrs = substr($str, 1, -1);
$utf8 = '';
$strlen_chrs = strlen($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
$ord_chrs_c = ord($chrs{$c});
switch (true) {
case $substr_chrs_c_2 == '\b':
$utf8 .= chr(0x08);
++$c;
break;
case $substr_chrs_c_2 == '\t':
$utf8 .= chr(0x09);
++$c;
break;
case $substr_chrs_c_2 == '\n':
$utf8 .= chr(0x0A);
++$c;
break;
case $substr_chrs_c_2 == '\f':
$utf8 .= chr(0x0C);
++$c;
break;
case $substr_chrs_c_2 == '\r':
$utf8 .= chr(0x0D);
++$c;
break;
case $substr_chrs_c_2 == '\\"':
case $substr_chrs_c_2 == '\\\'':
case $substr_chrs_c_2 == '\\\\':
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs{++$c};
}
break;
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
. chr(hexdec(substr($chrs, ($c + 4), 2)));
$utf8 .= $this->utf162utf8($utf16);
$c += 5;
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs{$c};
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 2);
++$c;
break;
case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 3);
$c += 2;
break;
case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 4);
$c += 3;
break;
case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 5);
$c += 4;
break;
case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 6);
$c += 5;
break;
}
}
return $utf8;
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str{0} == '[') {
$stk = array(SERVICES_JSON_IN_ARR);
$arr = array();
} else {
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = array();
} else {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = new stdClass();
}
}
array_push($stk, array('what' => SERVICES_JSON_SLICE,
'where' => 0,
'delim' => false));
$chrs = substr($str, 1, -1);
$chrs = $this->reduce_string($chrs);
if ($chrs == '') {
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} else {
return $obj;
}
}
//print("\nparsing {$chrs}\n");
$strlen_chrs = strlen($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
if (reset($stk) == SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
array_push($arr, $this->decode($slice));
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
$parts = array();
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// "name":value pair
$key = $this->decode($parts[1]);
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// name:value pair, where name is unquoted
$key = $parts[1];
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
}
}
} elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
//print("Found start of string at {$c}\n");
} elseif (($chrs{$c} == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far
array_pop($stk);
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '[') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
//print("Found start of array at {$c}\n");
} elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '{') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
//print("Found start of object at {$c}\n");
} elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
$c++;
//print("Found start of comment at {$c}\n");
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now
array_pop($stk);
$c++;
for ($i = $top['where']; $i <= $c; ++$i)
$chrs = substr_replace($chrs, ' ', $i, 1);
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
}
}
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
return $obj;
}
}
}
}
/**
* @todo Ultimately, this should just call PEAR::isError()
*/
function isError($data, $code = null)
{
if (class_exists('pear')) {
return PEAR::isError($data, $code);
} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
is_subclass_of($data, 'services_json_error'))) {
return true;
}
return false;
}
}
if (class_exists('PEAR_Error')) {
class Services_JSON_Error extends PEAR_Error
{
function Services_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
}
}
} else {
/**
* @todo Ultimately, this class shall be descended from PEAR_Error
*/
class Services_JSON_Error
{
function Services_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
}
}
}
?>

253
include/PasswordHash.php Normal file
View File

@@ -0,0 +1,253 @@
<?php
#
# Portable PHP password hashing framework.
#
# Version 0.3 / genuine.
#
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
# the public domain. Revised in subsequent years, still public domain.
#
# There's absolutely no warranty.
#
# The homepage URL for this framework is:
#
# http://www.openwall.com/phpass/
#
# Please be sure to update the Version line if you edit this file in any way.
# It is suggested that you leave the main version number intact, but indicate
# your project name (after the slash) and add your own revision information.
#
# Please do not change the "private" password hashing method implemented in
# here, thereby making your hashes incompatible. However, if you must, please
# change the hash type identifier (the "$P$") to something different.
#
# Obviously, since this code is in the public domain, the above are not
# requirements (there can be none), but merely suggestions.
#
class PasswordHash {
var $itoa64;
var $iteration_count_log2;
var $portable_hashes;
var $random_state;
function PasswordHash($iteration_count_log2, $portable_hashes)
{
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
$iteration_count_log2 = 8;
$this->iteration_count_log2 = $iteration_count_log2;
$this->portable_hashes = $portable_hashes;
$this->random_state = microtime();
if (function_exists('getmypid'))
$this->random_state .= getmypid();
}
function get_random_bytes($count)
{
$output = '';
if (is_readable('/dev/urandom') &&
($fh = @fopen('/dev/urandom', 'rb'))) {
$output = fread($fh, $count);
fclose($fh);
}
if (strlen($output) < $count) {
$output = '';
for ($i = 0; $i < $count; $i += 16) {
$this->random_state =
md5(microtime() . $this->random_state);
$output .=
pack('H*', md5($this->random_state));
}
$output = substr($output, 0, $count);
}
return $output;
}
function encode64($input, $count)
{
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];
if ($i < $count)
$value |= ord($input[$i]) << 8;
$output .= $this->itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count)
break;
if ($i < $count)
$value |= ord($input[$i]) << 16;
$output .= $this->itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count)
break;
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
function gensalt_private($input)
{
$output = '$P$';
$output .= $this->itoa64[min($this->iteration_count_log2 +
((PHP_VERSION >= '5') ? 5 : 3), 30)];
$output .= $this->encode64($input, 6);
return $output;
}
function crypt_private($password, $setting)
{
$output = '*0';
if (substr($setting, 0, 2) == $output)
$output = '*1';
$id = substr($setting, 0, 3);
# We use "$P$", phpBB3 uses "$H$" for the same thing
if ($id != '$P$' && $id != '$H$')
return $output;
$count_log2 = strpos($this->itoa64, $setting[3]);
if ($count_log2 < 7 || $count_log2 > 30)
return $output;
$count = 1 << $count_log2;
$salt = substr($setting, 4, 8);
if (strlen($salt) != 8)
return $output;
# We're kind of forced to use MD5 here since it's the only
# cryptographic primitive available in all versions of PHP
# currently in use. To implement our own low-level crypto
# in PHP would result in much worse performance and
# consequently in lower iteration counts and hashes that are
# quicker to crack (by non-PHP code).
if (PHP_VERSION >= '5') {
$hash = md5($salt . $password, TRUE);
do {
$hash = md5($hash . $password, TRUE);
} while (--$count);
} else {
$hash = pack('H*', md5($salt . $password));
do {
$hash = pack('H*', md5($hash . $password));
} while (--$count);
}
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16);
return $output;
}
function gensalt_extended($input)
{
$count_log2 = min($this->iteration_count_log2 + 8, 24);
# This should be odd to not reveal weak DES keys, and the
# maximum valid value is (2**24 - 1) which is odd anyway.
$count = (1 << $count_log2) - 1;
$output = '_';
$output .= $this->itoa64[$count & 0x3f];
$output .= $this->itoa64[($count >> 6) & 0x3f];
$output .= $this->itoa64[($count >> 12) & 0x3f];
$output .= $this->itoa64[($count >> 18) & 0x3f];
$output .= $this->encode64($input, 3);
return $output;
}
function gensalt_blowfish($input)
{
# This one needs to use a different order of characters and a
# different encoding scheme from the one in encode64() above.
# We care because the last character in our encoded string will
# only represent 2 bits. While two known implementations of
# bcrypt will happily accept and correct a salt string which
# has the 4 unused bits set to non-zero, we do not want to take
# chances and we also do not want to waste an additional byte
# of entropy.
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$output = '$2a$';
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
$output .= '$';
$i = 0;
do {
$c1 = ord($input[$i++]);
$output .= $itoa64[$c1 >> 2];
$c1 = ($c1 & 0x03) << 4;
if ($i >= 16) {
$output .= $itoa64[$c1];
break;
}
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 4;
$output .= $itoa64[$c1];
$c1 = ($c2 & 0x0f) << 2;
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 6;
$output .= $itoa64[$c1];
$output .= $itoa64[$c2 & 0x3f];
} while (1);
return $output;
}
function HashPassword($password)
{
$random = '';
if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
$random = $this->get_random_bytes(16);
$hash =
crypt($password, $this->gensalt_blowfish($random));
if (strlen($hash) == 60)
return $hash;
}
if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
if (strlen($random) < 3)
$random = $this->get_random_bytes(3);
$hash =
crypt($password, $this->gensalt_extended($random));
if (strlen($hash) == 20)
return $hash;
}
if (strlen($random) < 6)
$random = $this->get_random_bytes(6);
$hash =
$this->crypt_private($password,
$this->gensalt_private($random));
if (strlen($hash) == 34)
return $hash;
# Returning '*' on error is safe here, but would _not_ be safe
# in a crypt(3)-like function used _both_ for generating new
# hashes and for validating passwords against existing hashes.
return '*';
}
function CheckPassword($password, $stored_hash)
{
$hash = $this->crypt_private($password, $stored_hash);
if ($hash[0] == '*')
$hash = crypt($password, $stored_hash);
return $hash == $stored_hash;
}
}
?>

32
include/ajax.config.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
/*********************************************************************
ajax.content.php
AJAX interface for content fetching...allowed methods.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
if(!defined('INCLUDE_DIR')) die('!');
class ConfigAjaxAPI extends AjaxController {
//config info UI might need.
function ui() {
global $thisstaff, $cfg;
$config=array('ticket_lock_time'=>($cfg->getLockTime()*3600),
'max_attachments'=>$cfg->getMaxFileUploads(),
'max_file_size'=>$cfg->getMaxFileSize());
return $this->json_encode($config);
}
}
?>

83
include/ajax.content.php Normal file
View File

@@ -0,0 +1,83 @@
<?php
/*********************************************************************
ajax.content.php
AJAX interface for content fetching...allowed methods.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
if(!defined('INCLUDE_DIR')) die('!');
class ContentAjaxAPI extends AjaxController {
function log($id) {
if($id && ($log=Log::lookup($id))) {
$content=sprintf('<div style="width:500px;">&nbsp;<strong>%s</strong><br><p>%s</p>
<hr><strong>Log Date:</strong> <em>%s</em> <strong>IP Address:</strong> <em>%s</em></div>',
$log->getTitle(),
Format::display(str_replace(',',', ',$log->getText())),
Format::db_daydatetime($log->getCreateDate()),
$log->getIP());
}else {
$content='<div style="width:295px;">&nbsp;<strong>Error:</strong>Unknown or invalid log ID</div>';
}
return $content;
}
function ticket_variables() {
$content='<div style="width:600px;">
<h2>Ticket Variables</h2>
Please note that non-base variables depends on the context of use.
<br>
<table width="100%" border="0" cellspacing=1 cellpadding=2>
<tr><td width="50%" valign="top"><b>Base Variables</b></td><td><b>Other Variables</b></td></tr>
<tr>
<td width="50%" valign="top">
<table width="100%" border="0" cellspacing=1 cellpadding=1>
<tr><td width="100">%id</td><td>Ticket ID (internal ID)</td></tr>
<tr><td>%ticket</td><td>Ticket number (external ID)</td></tr>
<tr><td>%email</td><td>Email address</td></tr>
<tr><td>%name</td><td>Full name</td></tr>
<tr><td>%subject</td><td>Subject</td></tr>
<tr><td>%topic</td><td>Help topic (web only)</td></tr>
<tr><td>%phone</td><td>Phone number | ext</td></tr>
<tr><td>%status</td><td>Status</td></tr>
<tr><td>%priority</td><td>Priority</td></tr>
<tr><td>%dept</td><td>Department</td></tr>
<tr><td>%assigned</td><td>Assigned staff or team (if any)</td></tr>
<tr><td>%createdate</td><td>Date created</td></tr>
<tr><td>%duedate</td><td>Due date</td></tr>
<tr><td>%closedate</td><td>Date closed</td></tr>
</table>
</td>
<td valign="top">
<table width="100%" border="0" cellspacing=1 cellpadding=1>
<tr><td width="100">%message</td><td>Message (incoming)</td></tr>
<tr><td>%response</td><td>Response (outgoing)</td></tr>
<tr><td>%note</td><td>Internal/transfer note</td></tr>
<tr><td>%staff</td><td>Staff\'s name (alert/notices)</td></tr>
<tr><td>%assignee</td><td>Assigned staff</td></tr>
<tr><td>%assigner</td><td>Staff assigning the ticket</td></tr>
<tr><td>%url</td><td>osTicket\'s base url (FQDN)</td></tr>
</table>
</td>
</tr>
</table>
</div>';
return $content;
}
}
?>

83
include/ajax.kbase.php Normal file
View File

@@ -0,0 +1,83 @@
<?php
/*********************************************************************
ajax.kbase.php
AJAX interface for knowledge base related...allowed methods.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
if(!defined('INCLUDE_DIR')) die('!');
class KbaseAjaxAPI extends AjaxController {
function cannedResp($id, $format='') {
global $thisstaff, $_GET;
include_once(INCLUDE_DIR.'class.canned.php');
if(!$id || !($canned=Canned::lookup($id)) || !$canned->isEnabled())
Http::response(404, 'No such premade reply');
//Load ticket.
if($_GET['tid']) {
include_once(INCLUDE_DIR.'class.ticket.php');
$ticket = Ticket::lookup($_GET['tid']);
}
switch($format) {
case 'json':
$resp['id'] = $canned->getId();
$resp['ticket'] = $canned->getTitle();
$resp['response'] = $ticket?$ticket->replaceTemplateVars($canned->getResponse()):$canned->getResponse();
$resp['files'] = $canned->getAttachments();
$response = $this->json_encode($resp);
break;
case 'txt':
default:
$response =$ticket?$ticket->replaceTemplateVars($canned->getResponse()):$canned->getResponse();
}
return $response;
}
function faq($id, $format='html') {
global $thisstaff; //XXX: user ajax->getThisStaff()
include_once(INCLUDE_DIR.'class.faq.php');
if(!($faq=FAQ::lookup($id)))
return null;
//TODO: $fag->getJSON() for json format.
$resp = sprintf(
'<div style="width:650px;">
<strong>%s</strong><p>%s</p>
<div class="faded">Last updated %s</div>
<hr>
<a href="faq.php?id=%d">View</a> | <a href="faq.php?id=%d">Attachments (%s)</a>',
$faq->getQuestion(),
Format::safe_html($faq->getAnswer()),
Format::db_daydatetime($faq->getUpdateDate()),
$faq->getId(),
$faq->getId(),
$faq->getNumAttachments());
if($thisstaff && $thisstaff->canManageFAQ()) {
$resp.=sprintf(' | <a href="faq.php?id=%d&a=edit">Edit</a>',$faq->getId());
}
$resp.='</div>';
return $resp;
}
}
?>

164
include/ajax.tickets.php Normal file
View File

@@ -0,0 +1,164 @@
<?php
/*********************************************************************
ajax.tickets.php
AJAX interface for tickets
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
if(!defined('INCLUDE_DIR')) die('403');
include_once(INCLUDE_DIR.'class.ticket.php');
class TicketsAjaxAPI extends AjaxController {
function search() {
$limit = isset($_GET['limit']) ? (int) $_GET['limit']:25;
$items=array();
$ticketid=false;
if(isset($_GET['id'])){
$WHERE=' WHERE ticketID LIKE \''.db_input($_GET['id'], false).'%\'';
$ticketid=true;
}elseif(isset($_GET['email'])){
$WHERE=' WHERE email LIKE \''.db_input(strtolower($_GET['email']), false).'%\'';
}else{
Http::response(400, "id or email argument is required");
}
$sql='SELECT DISTINCT ticketID,email,name FROM '.TICKET_TABLE.' '.$WHERE.' ORDER BY created LIMIT '.$limit;
$res=db_query($sql);
if($res && db_num_rows($res)){
while(list($id,$email,$name)=db_fetch_row($res)) {
$info=($ticketid)?$email:$id;
$id=($ticketid)?$id:$email;
# TODO: Return 'name' from email address if 'email' argument
# specified?
$items[] = array('id'=>$id, 'value'=>$id, 'info'=>$info,
'name'=>$name);
}
}
return $this->encode(array('results'=>$items));
}
function acquireLock($tid) {
global $cfg,$thisstaff;
if(!$tid or !is_numeric($tid) or !$thisstaff or !$cfg)
return 0;
$ticket = Ticket::lookup($tid);
if(!$ticket || !$ticket->checkStaffAccess($thisstaff))
return $this->json_encode(array('id'=>0, 'retry'=>false, 'msg'=>'Lock denied!'));
//is the ticket already locked?
if($ticket->isLocked() && ($lock=$ticket->getLock()) && !$lock->isExpired()) {
/*Note: Ticket->acquireLock does the same logic...but we need it here since we need to know who owns the lock up front*/
//Ticket is locked by someone else.??
if($lock->getStaffId()!=$thisstaff->getId())
return $this->json_encode(array('id'=>0, 'retry'=>false, 'msg'=>'Unable to acquire lock.'));
//Ticket already locked by staff...try renewing it.
$lock->renew(); //New clock baby!
return $this->json_encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime()));
}
//Ticket is not locked or the lock is expired...try locking it...
if($lock=$ticket->acquireLock($thisstaff->getId(),$cfg->getLockTime())) //Set the lock.
return $this->json_encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime()));
//unable to obtain the lock..for some really weired reason!
//Client should watch for possible loop on retries. Max attempts?
return $this->json_encode(array('id'=>0, 'retry'=>true));
}
function renewLock($tid, $id) {
global $thisstaff;
if(!$id or !is_numeric($id) or !$thisstaff)
return $this->json_encode(array('id'=>0, 'retry'=>true));
$lock= TicketLock::lookup($id);
if(!$lock || !$lock->getStaffId() || $lock->isExpired()) //Said lock doesn't exist or is is expired
return self::acquireLock($tid); //acquire the lock
if($lock->getStaffId()!=$thisstaff->getId()) //user doesn't own the lock anymore??? sorry...try to next time.
return $this->json_encode(array('id'=>0, 'retry'=>false)); //Give up...
//Renew the lock.
$lock->renew(); //Failure here is not an issue since the lock is not expired yet.. client need to check time!
return $this->json_encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime()));
}
function releaseLock($tid, $id=0) {
global $thisstaff;
if($id && is_numeric($id)){ //Lock Id provided!
$lock = TicketLock::lookup($id, $tid);
//Already gone?
if(!$lock || !$lock->getStaffId() || $lock->isExpired()) //Said lock doesn't exist or is is expired
return 1;
//make sure the user actually owns the lock before releasing it.
return ($lock->getStaffId()==$thisstaff->getId() && $lock->release())?1:0;
}elseif($tid){ //release all the locks the user owns on the ticket.
return TicketLock::removeStaffLocks($thisstaff->getId(),$tid)?1:0;
}
return 0;
}
function previewTicket ($tid) {
global $thisstaff;
$ticket = new Ticket($tid);
$resp = sprintf(
'<div style="width:500px;">
<strong>Ticket #%d Preview</strong><br>INFO HERE!!',
$ticket->getExtId());
$options[]=array('action'=>'Thread ('.$ticket->getThreadCount().')','url'=>"tickets.php?id=$tid");
if($ticket->getNumNotes())
$options[]=array('action'=>'Notes ('.$ticket->getNumNotes().')','url'=>"tickets.php?id=$tid#notes");
if($ticket->isOpen())
$options[]=array('action'=>'Reply','url'=>"tickets.php?id=$tid#reply");
if($thisstaff->canAssignTickets())
$options[]=array('action'=>($ticket->isAssigned()?'Reassign':'Assign'),'url'=>"tickets.php?id=$tid#assign");
if($thisstaff->canTransferTickets())
$options[]=array('action'=>'Transfer','url'=>"tickets.php?id=$tid#transfer");
$options[]=array('action'=>'Post Note','url'=>"tickets.php?id=$tid#note");
if($options) {
$resp.='<ul class="tip_menu">';
foreach($options as $option) {
$resp.=sprintf('<li><a href="%s">%s</a></li>',
$option['url'],$option['action']);
}
$resp.='</ul>';
}
$resp.='</div>';
return $resp;
}
}
?>

71
include/api.ticket.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
include_once "include/class.api.php";
include_once "include/class.ticket.php";
class TicketController extends ApiController {
# Supported arguments -- anything else is an error. These items will be
# inspected _after_ the fixup() method of the ApiXxxDataParser classes
# so that all supported input formats should be supported
function getRequestStructure($format) {
$supported = array(
"alert", "autorespond", "source",
"name", "email", "subject", "phone", "phone_ext",
"attachments" => array("*" =>
array("name", "type", "data", "encoding")
),
"message", "ip"
);
if ($format == "xml") return array("ticket" => $supported);
else return $supported;
}
function create($format) {
$this->requireApiKey();
# Parse request body
$data = $this->getRequest($format);
if ($format == "xml") $data = $data["ticket"];
# Pull off some meta-data
$alert = $data['alert'] ? $data['alert'] : true;
$autorespond = $data['autorespond'] ? $data['autorespond'] : true;
$source = $data['source'] ? $data['source'] : 'API';
# TODO: Handle attachment encoding (base64)
foreach ($data["attachments"] as $filename=>&$info) {
if ($info["encoding"] == "base64") {
# XXX: May fail on large inputs. See
# http://us.php.net/manual/en/function.base64-decode.php#105512
if (!($info["data"] = base64_decode($info["data"], true)))
Http::response(400, sprintf(
"%s: Poorly encoded base64 data",
$filename));
}
$info['size'] = strlen($info['data']);
}
# Create the ticket with the data (attempt to anyway)
$errors = array();
$ticket = Ticket::create($data, $errors, $source, $autorespond,
$alert);
# Return errors (?)
if (count($errors)) {
Http::response(400, "Unable to create new ticket: validation errors:\n"
. Format::array_implode(": ", "\n", $errors));
} elseif (!$ticket) {
Http::response(500, "Unable to create new ticket: unknown error");
}
# Save attachment(s)
foreach ($data["attachments"] as &$info)
$ticket->saveAttachment($info, $ticket->getLastMsgId(), "M");
# All done. Return HTTP/201 --> Created
Http::response(201, $ticket->getExtId());
}
}
?>

53
include/class.ajax.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
/*********************************************************************
class.ajax.php
AjaxController class that is an extension of the ApiController class. It
will be used to provide functionality common to all Ajax API calls
Jared Hancock
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
require_once (INCLUDE_DIR.'class.api.php');
/**
* AjaxController Class
* A simple extension of the ApiController class that will assist in
* providing functionality common to all Ajax call controllers. Any Ajax
* call controller should inherit from this class in order to maintain
* consistency.
*/
class AjaxController extends ApiController {
function AjaxController() {
# Security checks first
# --> It is assumed that all AJAX calls will require a login. And
# for now, since client logins are not yet supported, a staff
# login will be required for AJAX calls.
$this->staffOnly();
}
function staffOnly() {
global $thisstaff;
if(!$thisstaff || !$thisstaff->isValid()) {
Http::response(401,'Access Denied. IP '.$_SERVER['REMOTE_ADDR']);
}
}
/**
* Convert a PHP array into a JSON-encoded string
*/
function json_encode($what) {
require_once (INCLUDE_DIR.'class.json.php');
$encoder = new JsonDataEncoder();
return $encoder->encode($what);
}
function encode($what) {
return $this->json_encode($what);
}
}

284
include/class.api.php Normal file
View File

@@ -0,0 +1,284 @@
<?php
/*********************************************************************
class.api.php
API
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class API {
var $id;
var $info;
function API($id){
$this->id=0;
$this->load($id);
}
function load($id) {
$sql='SELECT * FROM '.API_KEY_TABLE.' WHERE id='.db_input($id);
if(($res=db_query($sql)) && db_num_rows($res)) {
$info=db_fetch_array($res);
$this->id=$info['id'];
$this->info=$info;
return true;
}
return false;
}
function reload() {
return $this->load($this->getId());
}
function getId(){
return $this->id;
}
function getKey(){
return $this->info['apikey'];
}
function getIPAddr(){
return $this->info['ipaddr'];
}
function getNotes(){
return $this->info['notes'];
}
function isActive(){
return ($this->info['isactive']);
}
function update($vars,&$errors){
if(API::save($this->getId(),$vars,$errors)){
$this->reload();
return true;
}
return false;
}
function delete(){
$sql='DELETE FROM '.API_KEY_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1';
return (db_query($sql) && ($num=db_affected_rows()));
}
/** Static functions **/
function add($vars,&$errors){
return API::save(0,$vars,$errors);
}
function validate($key,$ip){
$sql='SELECT id FROM '.API_KEY_TABLE.' WHERE ipaddr='.db_input($ip).' AND apikey='.db_input($key);
return (($res=db_query($sql)) && db_num_rows($res));
}
function getKeyByIPAddr($ip){
$sql='SELECT apikey FROM '.API_KEY_TABLE.' WHERE ipaddr='.db_input($ip);
if(($res=db_query($sql)) && db_num_rows($res))
list($key)=db_fetch_row($res);
return $key;
}
function lookup($id){
return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null;
}
function save($id,$vars,&$errors){
if(!$id) {
if(!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr']))
$errors['ipaddr']='Valid IP required';
elseif(API::getKeyByIPAddr($vars['ipaddr']))
$errors['ipaddr']='API key for the IP already exists';
}
if($errors) return false;
$sql=' updated=NOW() '.
',isactive='.db_input($vars['isactive']).
',notes='.db_input($vars['notes']);
if($id) {
$sql='UPDATE '.API_KEY_TABLE.' SET '.$sql.' WHERE id='.db_input($id);
if(db_query($sql))
return true;
$errors['err']='Unable to update API key. Internal error occurred';
}else{
$sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql.',created=NOW() '.
',ipaddr='.db_input($vars['ipaddr']).
',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randcode(16)))));
if(db_query($sql) && ($id=db_insert_id()))
return $id;
$errors['err']='Unable to add API key. Internal error';
}
return false;
}
}
/**
* Controller for API methods. Provides methods to check to make sure the
* API key was sent and that the Client-IP and API-Key have been registered
* in the database, and methods for parsing and validating data sent in the
* API request.
*/
class ApiController {
function requireApiKey() {
# Validate the API key -- required to be sent via the X-API-Key
# header
if (!isset($_SERVER['HTTP_X_API_KEY']))
Http::response(403, "API key required");
else if (!Api::validate($_SERVER['HTTP_X_API_KEY'],
$_SERVER['REMOTE_ADDR']))
Http::response(401,
"API key not found or source IP not authorized");
}
/**
* Retrieves the body of the API request and converts it to a common
* hashtable. For JSON formats, this is mostly a noop, the conversion
* work will be done for XML requests
*/
function getRequest($format) {
if (!($stream = @fopen("php://input", "r")))
Http::response(400, "Unable to read request body");
if ($format == "xml") {
if (!function_exists("xml_parser_create"))
Http::response(500, "XML extension not supported");
$tree = new ApiXmlDataParser();
} elseif ($format == "json") {
$tree = new ApiJsonDataParser();
}
if (!($data = $tree->parse($stream)))
Http::response(400, $tree->lastError());
$this->validate($data, $this->getRequestStructure($format));
return $data;
}
/**
* Structure to validate the request against -- must be overridden to be
* useful
*/
function getRequestStructure($format) { return array(); }
/**
* Simple validation that makes sure the keys of a parsed request are
* expected. It is assumed that the functions actually implementing the
* API will further validate the contents of the request
*/
function validate($data, $structure, $prefix="") {
foreach ($data as $key=>$info) {
if (is_array($structure) and is_array($info)) {
$search = isset($structure[$key]) ? $key : "*";
if (isset($structure[$search])) {
$this->validate($info, $structure[$search], "$prefix$key/");
continue;
}
} elseif (in_array($key, $structure)) {
continue;
}
Http::response(400, "$prefix$key: Unexpected data received");
}
}
}
include_once "class.xml.php";
class ApiXmlDataParser extends XmlDataParser {
function parse($stream) {
return $this->fixup(parent::parse($stream));
}
/**
* Perform simple operations to make data consistent between JSON and
* XML data types
*/
function fixup($current) {
if (!is_array($current))
return $current;
foreach ($current as $key=>&$value) {
if ($key == "phone") {
$current["phone_ext"] = $value["ext"]; # PHP [like] point
$value = $value[":text"];
} else if ($key == "alert") {
$value = (bool)$value;
} else if ($key == "autorespond") {
$value = (bool)$value;
} else if ($key == "attachments") {
foreach ($value as &$info) {
$info["data"] = $info[":text"];
unset($info[":text"]);
}
unset($info);
}
if (is_array($value)) {
$value = $this->fixup($value);
}
}
return $current;
}
}
include_once "class.json.php";
class ApiJsonDataParser extends JsonDataParser {
function parse($stream) {
return $this->fixup(parent::parse($stream));
}
function fixup($current) {
if (!is_array($current))
return $current;
foreach ($current as $key=>&$value) {
if ($key == "phone") {
list($value,$current["phone_ext"])
= explode("X", strtoupper($value), 2);
} else if ($key == "alert") {
$value = (bool)$value;
} else if ($key == "autorespond") {
$value = (bool)$value;
} else if ($key == "attachments") {
foreach ($value as &$info) {
$data = reset($info);
# PHP5: fopen("data://$data[5:]");
if (substr($data, 0, 5) != "data:") {
$info = array(
"data" => $data,
"type" => "text/plain",
"name" => key($info));
} else {
$data = substr($data,5);
list($meta, $contents) = explode(",", $data);
list($type, $extra) = explode(";", $meta);
$info = array(
"data" => $contents,
"type" => $type,
"name" => key($info));
if (substr($extra, -6) == "base64")
$info["encoding"] = "base64";
# TODO: Handle 'charset' hint in $extra
}
}
unset($value);
}
if (is_array($value)) {
$value = $this->fixup($value);
}
}
return $current;
}
}
?>

View File

@@ -0,0 +1,103 @@
<?php
/*********************************************************************
class.attachment.php
Attachment Handler - mainly used for lookup...doesn't save!
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
require_once(INCLUDE_DIR.'class.ticket.php');
require_once(INCLUDE_DIR.'class.file.php');
class Attachment {
var $id;
var $file_id;
var $ticket_id;
var $info;
function Attachment($id,$tid=0) {
$sql='SELECT * FROM '.TICKET_ATTACHMENT_TABLE.' WHERE attach_id='.db_input($id);
if($tid)
$sql.=' AND ticket_id='.db_input($tid);
if(!($res=db_query($sql)) || !db_num_rows($res))
return false;
$this->ht=db_fetch_array($res);
$this->id=$this->ht['attach_id'];
$this->file_id=$this->ht['file_id'];
$this->ticket_id=$this->ht['ticket_id'];
$this->file=null;
$this->ticket=null;
return true;
}
function getId() {
return $this->id;
}
function getTicketId() {
return $this->ticket_id;
}
function getTicket() {
if(!$this->ticket && $this->getTicketId())
$this->ticket = Ticket::lookup($this->getTicketId());
return $this->ticket;
}
function getFileId() {
return $this->file_id;
}
function getFile() {
if(!$this->file && $this->getFileId())
$this->file = AttachmentFile::lookup($this->getFileId());
return $this->file;
}
function getCreateDate() {
return $this->ht['created'];
}
function getHashtable() {
return $this->ht;
}
function getInfo() {
return $this->getHashtable();
}
/* Static functions */
function getIdByFileHash($hash, $tid=0) {
$sql='SELECT attach_id FROM '.TICKET_ATTACHMENT_TABLE.' a '
.' INNER JOIN '.FILE_TABLE.' f ON(f.id=a.file_id) '
.' WHERE f.hash='.db_input($hash);
if($tid)
$sql.=' AND a.ticket_id='.db_input($tid);
return db_result(db_query($sql));
}
function lookup($id,$tid=0) {
$id=is_numeric($id)?$id:self::getIdByFileHash($hash,$tid);
return ($id && is_numeric($id) && ($attach = new Attachment($id,$tid)) && $attach->getId()==$id)?$attach:null;
}
}
?>

66
include/class.banlist.php Normal file
View File

@@ -0,0 +1,66 @@
<?php
/*********************************************************************
class.banlist.php
Banned email addresses handle.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
require_once "class.filter.php";
class Banlist {
function add($email,$submitter='') {
return self::getSystemBanList()->addRule('email','equal',$email);
}
function remove($email) {
return self::getSystemBanList()->removeRule('email','equal',$email);
}
function isbanned($email) {
return EmailFilter::isBanned($email);
}
function includes($email) {
return self::getSystemBanList()->containsRule('email','equal',$email);
}
function ensureSystemBanList() {
if (!($id=Filter::getIdByName('SYSTEM BAN LIST')))
$id=self::createSystemBanList();
return $id;
}
function createSystemBanList() {
# XXX: Filter::create should return the ID!!!
$errors=array();
return Filter::create(array(
'execorder' => 99,
'name' => 'SYSTEM BAN LIST',
'isactive' => 1,
'match_all_rules' => false,
'reject_email' => true,
'rules' => array(),
'notes' => 'Internal list for email banning. Do not remove'
), $errors);
}
function getSystemBanList() {
return new Filter(self::ensureSystemBanList());
}
function getFilter() {
return self::getSystemBanList();
}
}

272
include/class.canned.php Normal file
View File

@@ -0,0 +1,272 @@
<?php
/*********************************************************************
class.canned.php
Canned Responses AKA Premade replies
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
include_once(INCLUDE_DIR.'class.file.php');
class Canned {
var $id;
var $ht;
var $attachments;
function Canned($id){
$this->id=0;
$this->load($id);
}
function load($id=0) {
if(!$id && !($id=$this->getId()))
return false;
$sql='SELECT canned.*, count(attach.file_id) as attachments '
.' FROM '.CANNED_TABLE.' canned '
.' LEFT JOIN '.CANNED_ATTACHMENT_TABLE.' attach ON (attach.canned_id=canned.canned_id) '
.' WHERE canned.canned_id='.db_input($id);
if(!($res=db_query($sql)) || !db_num_rows($res))
return false;
$this->ht = db_fetch_array($res);
$this->id = $this->ht['canned_id'];
$this->attachments = array();
return true;
}
function reload() {
return $this->load();
}
function getId(){
return $this->id;
}
function isEnabled() {
return ($this->ht['isenabled']);
}
function isActive(){
return $this->isEnabled();
}
function getTitle() {
return $this->ht['title'];
}
function getResponse() {
return $this->ht['response'];
}
function getReply() {
return $this->getResponse();
}
function getNotes() {
return $this->ht['notes'];
}
function getDeptId(){
return $this->ht['dept_id'];
}
function getHashtable() {
return $this->ht;
}
function getInfo() {
return $this->getHashtable();
}
function update($vars, &$errors) {
if(!$this->save($this->getId(),$vars,$errors))
return false;
$this->reload();
return true;
}
function getNumAttachments() {
return $this->ht['attachments'];
}
function getAttachments() {
if(!$this->attachments && $this->getNumAttachments()) {
$sql='SELECT f.id, f.size, f.hash, f.name '
.' FROM '.FILE_TABLE.' f '
.' INNER JOIN '.CANNED_ATTACHMENT_TABLE.' a ON(f.id=a.file_id) '
.' WHERE a.canned_id='.db_input($this->getId());
$this->attachments = array();
if(($res=db_query($sql)) && db_num_rows($res)) {
while($rec=db_fetch_array($res)) {
$rec['key'] =md5($rec['id'].session_id().$rec['hash']);
$this->attachments[] = $rec;
}
}
}
return $this->attachments;
}
/*
@files is an array - hash table of multiple attachments.
*/
function uploadAttachments($files) {
foreach($files as $file) {
if(($fileId=is_numeric($file)?$file:AttachmentFile::upload($file)) && is_numeric($fileId)) {
$sql ='INSERT INTO '.CANNED_ATTACHMENT_TABLE
.' SET canned_id='.db_input($this->getId()).', file_id='.db_input($fileId);
if(db_query($sql)) $i++;
}
}
if($i) $this->reload();
return $i;
}
function deleteAttachment($fileId) {
$sql='DELETE FROM '.CANNED_ATTACHMENT_TABLE
.' WHERE canned_id='.db_input($this->getId())
.' AND file_id='.db_input($fileId)
.' LIMIT 1';
if(!db_query($sql) || !db_affected_rows())
return false;
if(($file=AttachmentFile::lookup($fileId)) && !$file->isInuse())
$file->delete();
return true;
}
function deleteAttachments(){
$deleted=0;
if(($attachments = $this->getAttachments())) {
foreach($attachments as $attachment)
if($attachment['id'] && $this->deleteAttachment($attachment['id']))
$deleted++;
}
return $deleted;
}
function delete(){
$sql='DELETE FROM '.CANNED_TABLE.' WHERE canned_id='.db_input($this->getId()).' LIMIT 1';
if(db_query($sql) && ($num=db_affected_rows())) {
$this->deleteAttachments();
}
return $num;
}
/*** Static functions ***/
function lookup($id){
return ($id && is_numeric($id) && ($c= new Canned($id)) && $c->getId()==$id)?$c:null;
}
function create($vars,&$errors) {
return self::save(0,$vars,$errors);
}
function getIdByTitle($titke) {
$sql='SELECT canned_id FROM '.CANNED_TABLE.' WHERE title='.db_input($title);
if(($res=db_query($sql)) && db_num_rows($res))
list($id)=db_fetch_row($res);
return $id;
}
function getCannedResponses($deptId=0, $explicit=false) {
$sql='SELECT canned_id, title FROM '.CANNED_TABLE;
if($deptId){
$sql.=' WHERE dept_id='.db_input($deptId);
if(!$explicit)
$sql.=' OR dept_id=0';
}
$sql.=' ORDER BY title';
$responses = array();
if(($res=db_query($sql)) && db_num_rows($res)) {
while(list($id,$title)=db_fetch_row($res))
$responses[$id]=$title;
}
return $responses;
}
function responsesByDeptId($deptId, $explicit=false) {
return self::getCannedResponses($deptId, $explicit);
}
function save($id,$vars,&$errors) {
//We're stripping html tags - until support is added to tickets.
$vars['title']=Format::striptags(trim($vars['title']));
$vars['response']=Format::striptags(trim($vars['response']));
$vars['notes']=Format::striptags(trim($vars['notes']));
if($id && $id!=$vars['id'])
$errors['err']='Internal error. Try again';
if(!$vars['title'])
$errors['title']='Title required';
elseif(strlen($vars['title'])<3)
$errors['title']='Title is too short. 3 chars minimum';
elseif(($cid=self::getIdByTitle($vars['title'])) && $cid!=$id)
$errors['title']='Title already exists';
if(!$vars['response'])
$errors['response']='Response text required';
if($errors) return false;
$sql=' updated=NOW() '.
',dept_id='.db_input($vars['dept_id']?$vars['dept_id']:0).
',isenabled='.db_input($vars['isenabled']).
',title='.db_input($vars['title']).
',response='.db_input($vars['response']).
',notes='.db_input($vars['notes']);
if($id) {
$sql='UPDATE '.CANNED_TABLE.' SET '.$sql.' WHERE canned_id='.db_input($id);
if(db_query($sql))
return true;
$errors['err']='Unable to update canned reply.';
} else {
$sql='INSERT INTO '.CANNED_TABLE.' SET '.$sql.',created=NOW()';
if(db_query($sql) && ($id=db_insert_id()))
return $id;
$errors['err']='Unable to create the canned reply. Internal error';
}
return false;
}
}
?>

54
include/class.captcha.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
/*********************************************************************
class.captcha.php
Very basic captcha class.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class Captcha {
var $hash;
var $bgimages=array('cottoncandy.png','grass.png','ripple.png','silk.png','whirlpool.png',
'bubbles.png','crackle.png','lines.png','sand.png','snakeskin.png');
var $font = 10;
function Captcha($len=6,$font=7,$bg=''){
$this->hash = strtoupper(substr(md5(rand(0, 9999)),rand(0, 24),$len));
$this->font = $font;
if($bg && !is_dir($bg)){ //bg file provided?
$this->bgimg=$bg;
}else{ //assume dir provided or defaults to local.
$this->bgimg=rtrim($bg,'/').'/'.$this->bgimages[array_rand($this->bgimages, 1)];
}
}
function getImage(){
if(!extension_loaded('gd') || !function_exists('gd_info')) //GD ext required.
return;
$_SESSION['captcha'] =''; //Clear
list($w,$h) = getimagesize($this->bgimg);
$x = round(($w/2)-((strlen($this->hash)*imagefontwidth($this->font))/2), 1);
$y = round(($h/2)-(imagefontheight($this->font)/2));
$img= imagecreatefrompng($this->bgimg);
imagestring($img,$this->font, $x, $y,$this->hash,imagecolorallocate($img,0, 0, 0));
Header ("(captcha-content-type:) image/png");
imagepng($img);
imagedestroy($img);
$_SESSION['captcha'] = md5($this->hash);
}
}
?>

167
include/class.category.php Normal file
View File

@@ -0,0 +1,167 @@
<?php
/*********************************************************************
class.category.php
Backend support for article categories.
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class Category {
var $id;
var $ht;
function Category($id) {
$this->id=0;
$this->load($id);
}
function load($id) {
$sql=' SELECT cat.*,count(faq.faq_id) as faqs '
.' FROM '.FAQ_CATEGORY_TABLE.' cat '
.' LEFT JOIN '.FAQ_TABLE.' faq ON(faq.category_id=cat.category_id) '
.' WHERE cat.category_id='.db_input($id)
.' GROUP BY cat.category_id';
if (!($res=db_query($sql)) || !db_num_rows($res))
return false;
$this->ht = db_fetch_array($res);
$this->id = $this->ht['category_id'];
return true;
}
function reload() {
return $this->load($this->getId());
}
/* ------------------> Getter methods <--------------------- */
function getId() { return $this->id; }
function getName() { return $this->ht['name']; }
function getNumFAQs() { return $this->ht['faqs']; }
function getDescription() { return $this->ht['description']; }
function getNotes() { return $this->ht['notes']; }
function getCreateDate() { return $this->ht['created']; }
function getUpdateDate() { return $this->ht['updated']; }
function isPublic() { return ($this->ht['ispublic']); }
function getHashtable() { return $this->ht; }
/* ------------------> Setter methods <--------------------- */
function setName($name) { $this->ht['name']=$name; }
function setNotes($notes) { $this->ht['notes']=$notes; }
function setDescription($desc) { $this->ht['description']=$desc; }
/* --------------> Database access methods <---------------- */
function update($vars, &$errors) {
if(!$this->save($this->getId(), $vars, $errors))
return false;
//TODO: move FAQs if requested.
$this->reload();
return true;
}
function delete() {
$sql='DELETE FROM '.FAQ_CATEGORY_TABLE
.' WHERE category_id='.db_input($this->getId())
.' LIMIT 1';
if(db_query($sql) && ($num=db_affected_rows())) {
db_query('DELETE FROM '.FAQ_TABLE
.' WHERE category_id='.db_input($this->getId()));
}
return $num;
}
/* ------------------> Static methods <--------------------- */
function lookup($id) {
return ($id && is_numeric($id) && ($c = new Category($id)))?$c:null;
}
function findIdByName($name) {
$sql='SELECT category_id FROM '.FAQ_CATEGORY_TABLE.' WHERE name='.db_input($name);
list($id) = db_fetch_row(db_query($sql));
return $id;
}
function findByName($name) {
if(($id=self::findIdByName($name)))
return new Category($id);
return false;
}
function validate($vars, &$errors) {
return self::save(0, $vars, $errors,true);
}
function create($vars, &$errors) {
return self::save(0, $vars, $errors);
}
function save($id, $vars, &$errors, $validation=false) {
//Cleanup.
$vars['name']=Format::striptags(trim($vars['name']));
//validate
if($id && $id!=$vars['id'])
$errors['err']='Internal error. Try again';
if(!$vars['name'])
$errors['name']='Category name is required';
elseif(strlen($vars['name'])<3)
$errors['name']='Name is too short. 3 chars minimum';
elseif(($cid=self::findIdByName($vars['name'])) && $cid!=$id)
$errors['name']='Category already exists';
if(!$vars['description'])
$errors['description']='Category description is required';
if($errors) return false;
/* validation only */
if($validation) return true;
//save
$sql=' updated=NOW() '.
',ispublic='.db_input(isset($vars['ispublic'])?$vars['ispublic']:0).
',name='.db_input($vars['name']).
',description='.db_input(Format::safe_html($vars['description'])).
',notes='.db_input($vars['notes']);
if($id) {
$sql='UPDATE '.FAQ_CATEGORY_TABLE.' SET '.$sql.' WHERE category_id='.db_input($id);
if(db_query($sql))
return true;
$errors['err']='Unable to update FAQ category.';
} else {
$sql='INSERT INTO '.FAQ_CATEGORY_TABLE.' SET '.$sql.',created=NOW()';
if(db_query($sql) && ($id=db_insert_id()))
return $id;
$errors['err']='Unable to create FAQ category. Internal error';
}
return false;
}
}
?>

93
include/class.client.php Normal file
View File

@@ -0,0 +1,93 @@
<?php
/*********************************************************************
class.client.php
Handles everything about client
The class will undergo major changes one client's accounts are used.
At the moment we will play off the email + ticket ID authentication.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class Client {
var $id;
var $fullname;
var $username;
var $passwd;
var $email;
var $udata;
var $ticket_id;
var $ticketID;
function Client($email,$id){
$this->id =0;
$this->load($id,$email);
}
function isClient(){
return TRUE;
}
function load($id,$email=''){
$sql='SELECT ticket_id,ticketID,name,email FROM '.TICKET_TABLE.' WHERE ticketID='.db_input($id);
if($email){ //don't validate...using whatever is entered.
$sql.=' AND email='.db_input($email);
}
$res=db_query($sql);
if(!$res || !db_num_rows($res))
return NULL;
$row=db_fetch_array($res);
$this->udata=$row;
$this->id = $row['ticketID']; //placeholder
$this->ticket_id = $row['ticket_id'];
$this->ticketID = $row['ticketID'];
$this->fullname = ucfirst($row['name']);
$this->username = $row['email'];
$this->email = $row['email'];
return($this->id);
}
function getId(){
return $this->id;
}
function getEmail(){
return $this->email;
}
function getUserName(){
return $this->username;
}
function getName(){
return $this->fullname;
}
function getTicketID() {
return $this->ticketID;
}
/* ------------- Static ---------------*/
function lookup($id, $email) {
return ($id && is_numeric($id) && ($c=new Client($id,$email)) && $c->getId()==$id)?$c:null;
}
}
?>

841
include/class.config.php Normal file
View File

@@ -0,0 +1,841 @@
<?php
/*********************************************************************
class.config.php
osTicket config info manager.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
require_once(INCLUDE_DIR.'class.email.php');
class Config {
var $id=0;
var $mysqltzoffset=0;
var $config=array();
var $defaultDept; //Default Department
var $defaultSLA; //Default SLA
var $defaultEmail; //Default Email
var $alertEmail; //Alert Email
var $defaultSMTPEmail; //Default SMTP Email
function Config($id) {
$this->load($id);
}
function load($id) {
$sql='SELECT * FROM '.CONFIG_TABLE.' WHERE id='.db_input($id);
if($id && ($res=db_query($sql)) && db_num_rows($res)) {
$this->config=db_fetch_array($res);
$this->id=$this->config['id'];
return true;
}
return false;
}
//Initialize some default values.
function init() {
list($mysqltz)=db_fetch_row(db_query('SELECT @@session.time_zone '));
$this->setMysqlTZ($mysqltz);
}
function reload() {
if($this->load($this->id))
$this->init();
}
function isHelpDeskOffline() {
return !$this->isSystemOnline();
}
function isSystemOnline() {
return ($this->config['isonline']);
}
function isKnowledgebaseEnabled() {
require_once(INCLUDE_DIR.'class.faq.php');
return ($this->config['enable_kb'] && FAQ::countPublishedFAQs());
}
function getVersion() {
return '1.7 DPR';
}
function getSchemaVersion() {
return $this->config['schema_signature'];
}
function setMysqlTZ($tz) {
//TODO: Combine the 2 replace regex
if($tz=='SYSTEM')
$this->mysqltzoffset=preg_replace('/([+-]\d{2})(\d{2})/','\1',date('O'));
else
$this->mysqltzoffset=preg_replace('/([+-]\d{2})(:)(\d{2})/','\1',$tz);
}
function getMysqlTZoffset() {
return $this->mysqltzoffset;
}
/* Date & Time Formats */
function observeDaylightSaving() {
return ($this->config['enable_daylight_saving']);
}
function getTimeFormat() {
return $this->config['time_format'];
}
function getDateFormat() {
return $this->config['date_format'];
}
function getDateTimeFormat() {
return $this->config['datetime_format'];
}
function getDayDateTimeFormat() {
return $this->config['daydatetime_format'];
}
function getId() {
return $this->config['id'];
}
function getTitle() {
return $this->config['helpdesk_title'];
}
function getUrl() {
return $this->config['helpdesk_url'];
}
function getBaseUrl() { //Same as above with no trailing slash.
return rtrim($this->getUrl(),'/');
}
function getConfig() {
return $this->config;
}
function getTZOffset() {
return $this->config['timezone_offset'];
}
function getPageSize() {
return $this->config['max_page_size'];
}
function getGracePeriod() {
return $this->config['overdue_grace_period'];
}
function getPasswdResetPeriod() {
return $this->config['passwd_reset_period'];
}
function getClientTimeout() {
return $this->getClientSessionTimeout();
}
function getClientSessionTimeout() {
return $this->config['client_session_timeout']*60;
}
function getClientLoginTimeout() {
return $this->config['client_login_timeout']*60;
}
function getClientMaxLogins() {
return $this->config['client_max_logins'];
}
function getStaffTimeout() {
return $this->getStaffSessionTimeout();
}
function getStaffSessionTimeout() {
return $this->config['staff_session_timeout']*60;
}
function getStaffLoginTimeout() {
return $this->config['staff_login_timeout']*60;
}
function getStaffMaxLogins() {
return $this->config['staff_max_logins'];
}
function getLockTime() {
return $this->config['autolock_minutes'];
}
function getDefaultDeptId() {
return $this->config['default_dept_id'];
}
function getDefaultDept() {
if(!$this->defaultDept && $this->getDefaultDeptId())
$this->defaultDept=Dept::lookup($this->getDefaultDeptId());
return $this->defaultDept;
}
function getDefaultEmailId() {
return $this->config['default_email_id'];
}
function getDefaultEmail() {
if(!$this->defaultEmail && $this->getDefaultEmailId())
$this->defaultEmail=Email::lookup($this->getDefaultEmailId());
return $this->defaultEmail;
}
function getDefaultEmailAddress() {
$email=$this->getDefaultEmail();
return $email?$email->getAddress():null;
}
function getDefaultSLAId() {
return $this->config['default_sla_id'];
}
function getDefaultSLA() {
if(!$this->defaultSLA && $this->getDefaultSLAId())
$this->defaultSLA=SLA::lookup($this->getDefaultSLAId());
return $this->defaultSLA;
}
function getAlertEmailId() {
return $this->config['alert_email_id'];
}
function getAlertEmail() {
if(!$this->alertEmail && $this->config['alert_email_id'])
$this->alertEmail= new Email($this->config['alert_email_id']);
return $this->alertEmail;
}
function getDefaultSMTPEmail() {
if(!$this->defaultSMTPEmail && $this->config['default_smtp_id'])
$this->defaultSMTPEmail= new Email($this->config['default_smtp_id']);
return $this->defaultSMTPEmail;
}
function allowSMTPSpoofing() {
return $this->config['spoof_default_smtp'];
}
function getDefaultPriorityId() {
return $this->config['default_priority_id'];
}
function getDefaultTemplateId() {
return $this->config['default_template_id'];
}
function getDefaultTemplate() {
if(!$this->defaultTemplate && $this->getDefaultTemplateId())
$this->defaultTemplate = Template::lookup($this->getDefaultTemplateId());
return $this->defaultTemplate;
}
function getMaxOpenTickets() {
return $this->config['max_open_tickets'];
}
function getMaxFileSize() {
return $this->config['max_file_size'];
}
function getMaxFileUploads() {
return $this->config['max_staff_file_uploads'];
}
function getLogLevel() {
return $this->config['log_level'];
}
function getLogGracePeriod() {
return $this->config['log_graceperiod'];
}
function logTicketActivity() {
return $this->config['log_ticket_activity'];
}
function clickableURLS() {
return ($this->config['clickable_urls']);
}
function canFetchMail() {
return ($this->config['enable_mail_fetch']);
}
function enableStaffIPBinding() {
return ($this->config['staff_ip_binding']);
}
function enableCaptcha() {
return (extension_loaded('gd') && function_exists('gd_info') && $this->config['enable_captcha']);
}
function enableAutoCron() {
return ($this->config['enable_auto_cron']);
}
function enableEmailPiping() {
return ($this->config['enable_email_piping']);
}
function allowPriorityChange() {
return ($this->config['allow_priority_change']);
}
function useEmailPriority() {
return ($this->config['use_email_priority']);
}
function getAdminEmail() {
return $this->config['admin_email'];
}
function getReplySeparator() {
return $this->config['reply_separator'];
}
function stripQuotedReply() {
return ($this->config['strip_quoted_reply']);
}
function saveEmailHeaders() {
return true; //No longer an option...hint: big plans for headers coming!!
}
function useRandomIds() {
return ($this->config['random_ticket_ids']);
}
/* autoresponders & Alerts */
function autoRespONNewTicket() {
return ($this->config['ticket_autoresponder']);
}
function autoRespONNewMessage() {
return ($this->config['message_autoresponder']);
}
function notifyONNewStaffTicket() {
return ($this->config['ticket_notice_active']);
}
function alertONNewMessage() {
return ($this->config['message_alert_active']);
}
function alertLastRespondentONNewMessage() {
return ($this->config['message_alert_laststaff']);
}
function alertAssignedONNewMessage() {
return ($this->config['message_alert_assigned']);
}
function alertDeptManagerONNewMessage() {
return ($this->config['message_alert_dept_manager']);
}
function alertONNewNote() {
return ($this->config['note_alert_active']);
}
function alertLastRespondentONNewNote() {
return ($this->config['note_alert_laststaff']);
}
function alertAssignedONNewNote() {
return ($this->config['note_alert_assigned']);
}
function alertDeptManagerONNewNote() {
return ($this->config['note_alert_dept_manager']);
}
function alertONNewTicket() {
return ($this->config['ticket_alert_active']);
}
function alertAdminONNewTicket() {
return ($this->config['ticket_alert_admin']);
}
function alertDeptManagerONNewTicket() {
return ($this->config['ticket_alert_dept_manager']);
}
function alertDeptMembersONNewTicket() {
return ($this->config['ticket_alert_dept_members']);
}
function alertONTransfer() {
return ($this->config['transfer_alert_active']);
}
function alertAssignedONTransfer() {
return ($this->config['transfer_alert_assigned']);
}
function alertDeptManagerONTransfer() {
return ($this->config['transfer_alert_dept_manager']);
}
function alertDeptMembersONTransfer() {
return ($this->config['transfer_alert_dept_members']);
}
function alertONAssignment() {
return ($this->config['assigned_alert_active']);
}
function alertStaffONAssignment() {
return ($this->config['assigned_alert_staff']);
}
function alertTeamLeadONAssignment() {
return ($this->config['assigned_alert_team_lead']);
}
function alertTeamMembersONAssignment() {
return ($this->config['assigned_alert_team_members']);
}
function alertONOverdueTicket() {
return ($this->config['overdue_alert_active']);
}
function alertAssignedONOverdueTicket() {
return ($this->config['overdue_alert_assigned']);
}
function alertDeptManagerONOverdueTicket() {
return ($this->config['overdue_alert_dept_manager']);
}
function alertDeptMembersONOverdueTicket() {
return ($this->config['overdue_alert_dept_members']);
}
function autoAssignReopenedTickets() {
return ($this->config['auto_assign_reopened_tickets']);
}
function showAssignedTickets() {
return ($this->config['show_assigned_tickets']);
}
function showAnsweredTickets() {
return ($this->config['show_answered_tickets']);
}
function hideStaffName() {
return ($this->config['hide_staff_name']);
}
function sendOverLimitNotice() {
return ($this->config['overlimit_notice_active']);
}
/* Error alerts sent to admin email when enabled */
function alertONSQLError() {
return ($this->config['send_sql_errors']);
}
function alertONLoginError() {
return ($this->config['send_login_errors']);
}
function alertONMailParseError() {
return ($this->config['send_mailparse_errors']);
}
/* Attachments */
function emailAttachments() {
return ($this->config['email_attachments']);
}
function allowAttachments() {
return ($this->config['allow_attachments']);
}
function allowOnlineAttachments() {
return ($this->allowAttachments() && $this->config['allow_online_attachments']);
}
function allowAttachmentsOnlogin() {
return ($this->allowOnlineAttachments() && $this->config['allow_online_attachments_onlogin']);
}
function allowEmailAttachments() {
return ($this->allowAttachments() && $this->config['allow_email_attachments']);
}
function getUploadDir() {
return $this->config['upload_dir'];
}
//simply checking if destination dir is usable..nothing to do with permission to upload!
function canUploadFiles() {
$dir=$this->config['upload_dir'];
return ($dir && is_writable($dir))?TRUE:FALSE;
}
function canUploadFileType($filename) {
$ext = strtolower(preg_replace("/.*\.(.{3,4})$/", "$1", $filename));
$allowed=$this->config['allowed_filetypes']?array_map('trim',explode(',',strtolower($this->config['allowed_filetypes']))):null;
return ($ext && is_array($allowed) && (in_array(".$ext",$allowed) || in_array(".*",$allowed)))?TRUE:FALSE;
}
function updateSettings($vars,&$errors) {
if(!$vars || $errors)
return false;
switch(strtolower($vars['t'])) {
case 'general':
return $this->updateGeneralSetting($vars,$errors);
break;
case 'dates':
return $this->updateDateTimeSetting($vars,$errors);
break;
case 'tickets':
return $this->updateTicketsSetting($vars,$errors);
break;
case 'emails':
return $this->updateEmailsSetting($vars,$errors);
break;
case 'attachments':
return $this->updateAttachmentsSetting($vars,$errors);
break;
case 'autoresponders':
return $this->updateAutoresponderSetting($vars,$errors);
break;
case 'alerts':
return $this->updateAlertsSetting($vars,$errors);
break;
case 'kb':
return $this->updateKBSetting($vars,$errors);
break;
default:
$errors['err']='Unknown setting option. Get technical support.';
}
return false;
}
function updateGeneralSetting($vars,&$errors) {
$f=array();
$f['helpdesk_url']=array('type'=>'string', 'required'=>1, 'error'=>'Helpdesk URl required');
$f['helpdesk_title']=array('type'=>'string', 'required'=>1, 'error'=>'Helpdesk title required');
$f['default_dept_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default Dept. required');
$f['default_template_id']=array('type'=>'int', 'required'=>1, 'error'=>'You must select template.');
$f['staff_session_timeout']=array('type'=>'int', 'required'=>1, 'error'=>'Enter idle time in minutes');
$f['client_session_timeout']=array('type'=>'int', 'required'=>1, 'error'=>'Enter idle time in minutes');
if(!Validator::process($f,$vars,$errors) || $errors)
return false;
$sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',isonline='.db_input($vars['isonline']).
',helpdesk_title='.db_input($vars['helpdesk_title']).
',helpdesk_url='.db_input($vars['helpdesk_url']).
',default_dept_id='.db_input($vars['default_dept_id']).
',default_template_id='.db_input($vars['default_template_id']).
',max_page_size='.db_input($vars['max_page_size']).
',log_level='.db_input($vars['log_level']).
',log_graceperiod='.db_input($vars['log_graceperiod']).
',passwd_reset_period='.db_input($vars['passwd_reset_period']).
',staff_max_logins='.db_input($vars['staff_max_logins']).
',staff_login_timeout='.db_input($vars['staff_login_timeout']).
',staff_session_timeout='.db_input($vars['staff_session_timeout']).
',staff_ip_binding='.db_input(isset($vars['staff_ip_binding'])?1:0).
',client_max_logins='.db_input($vars['client_max_logins']).
',client_login_timeout='.db_input($vars['client_login_timeout']).
',client_session_timeout='.db_input($vars['client_session_timeout']).
',clickable_urls='.db_input(isset($vars['clickable_urls'])?1:0).
',enable_auto_cron='.db_input(isset($vars['enable_auto_cron'])?1:0).
' WHERE id='.$this->getId();
return (db_query($sql));
}
function updateDateTimeSetting($vars,&$errors) {
$f=array();
$f['time_format']=array('type'=>'string', 'required'=>1, 'error'=>'Time format required');
$f['date_format']=array('type'=>'string', 'required'=>1, 'error'=>'Date format required');
$f['datetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Datetime format required');
$f['daydatetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Day, Datetime format required');
$f['default_timezone_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default Timezone required');
if(!Validator::process($f,$vars,$errors) || $errors)
return false;
$sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',time_format='.db_input($vars['time_format']).
',date_format='.db_input($vars['date_format']).
',datetime_format='.db_input($vars['datetime_format']).
',daydatetime_format='.db_input($vars['daydatetime_format']).
',default_timezone_id='.db_input($vars['default_timezone_id']).
',enable_daylight_saving='.db_input(isset($vars['enable_daylight_saving'])?1:0).
' WHERE id='.$this->getId();
return (db_query($sql));
}
function updateTicketsSetting($vars,&$errors) {
$f=array();
$f['default_sla_id']=array('type'=>'int', 'required'=>1, 'error'=>'Selection required');
$f['default_priority_id']=array('type'=>'int', 'required'=>1, 'error'=>'Selection required');
$f['max_open_tickets']=array('type'=>'int', 'required'=>1, 'error'=>'Enter valid numeric value');
$f['autolock_minutes']=array('type'=>'int', 'required'=>1, 'error'=>'Enter lock time in minutes');
if($vars['enable_captcha']) {
if (!extension_loaded('gd'))
$errors['enable_captcha']='The GD extension required';
elseif(!function_exists('imagepng'))
$errors['enable_captcha']='PNG support required for Image Captcha';
}
if(!Validator::process($f,$vars,$errors) || $errors)
return false;
$sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',random_ticket_ids='.db_input($vars['random_ticket_ids']).
',default_priority_id='.db_input($vars['default_priority_id']).
',default_sla_id='.db_input($vars['default_sla_id']).
',max_open_tickets='.db_input($vars['max_open_tickets']).
',autolock_minutes='.db_input($vars['autolock_minutes']).
',allow_priority_change='.db_input(isset($vars['allow_priority_change'])?1:0).
',use_email_priority='.db_input(isset($vars['use_email_priority'])?1:0).
',enable_captcha='.db_input(isset($vars['enable_captcha'])?1:0).
',log_ticket_activity='.db_input(isset($vars['log_ticket_activity'])?1:0).
',auto_assign_reopened_tickets='.db_input(isset($vars['auto_assign_reopened_tickets'])?1:0).
',show_assigned_tickets='.db_input(isset($vars['show_assigned_tickets'])?1:0).
',show_answered_tickets='.db_input(isset($vars['show_answered_tickets'])?1:0).
',show_related_tickets='.db_input(isset($vars['show_related_tickets'])?1:0).
',hide_staff_name='.db_input(isset($vars['hide_staff_name'])?1:0);
return (db_query($sql));
}
function updateEmailsSetting($vars,&$errors) {
$f=array();
$f['default_email_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default email required');
$f['alert_email_id']=array('type'=>'int', 'required'=>1, 'error'=>'Selection required');
$f['admin_email']=array('type'=>'email', 'required'=>1, 'error'=>'System admin email required');
if($vars['strip_quoted_reply'] && !$vars['reply_separator'])
$errors['reply_separator']='Reply separator required to strip quoted reply.';
if($vars['admin_email'] && Email::getIdByEmail($vars['admin_email'])) //Make sure admin email is not also a system email.
$errors['admin_email']='Email already setup as system email';
if(!Validator::process($f,$vars,$errors) || $errors)
return false;
$sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',default_email_id='.db_input($vars['default_email_id']).
',alert_email_id='.db_input($vars['alert_email_id']).
',default_smtp_id='.db_input($vars['default_smtp_id']).
',admin_email='.db_input($vars['admin_email']).
',enable_mail_polling='.db_input(isset($vars['enable_mail_polling'])?1:0).
',enable_email_piping='.db_input(isset($vars['enable_email_piping'])?1:0).
',strip_quoted_reply='.db_input(isset($vars['strip_quoted_reply'])?1:0).
',reply_separator='.db_input($vars['reply_separator']);
return (db_query($sql));
}
function updateAttachmentsSetting($vars,&$errors) {
if($vars['allow_attachments']) {
if(!ini_get('file_uploads'))
$errors['err']='The \'file_uploads\' directive is disabled in php.ini';
if(!is_numeric($vars['max_file_size']))
$errors['max_file_size']='Maximum file size required';
if(!$vars['allowed_filetypes'])
$errors['allowed_filetypes']='Allowed file extentions required';
if(!($maxfileuploads=ini_get('max_file_uploads')))
$maxfileuploads=DEFAULT_MAX_FILE_UPLOADS;
if(!$vars['max_user_file_uploads'] || $vars['max_user_file_uploads']>$maxfileuploads)
$errors['max_user_file_uploads']='Invalid selection';
if(!$vars['max_staff_file_uploads'] || $vars['max_staff_file_uploads']>$maxfileuploads)
$errors['max_staff_file_uploads']='Invalid selection';
}
if($errors) return false;
$sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',allow_attachments='.db_input(isset($vars['allow_attachments'])?1:0).
',allowed_filetypes='.db_input(strtolower(preg_replace("/\n\r|\r\n|\n|\r/", '',trim($vars['allowed_filetypes'])))).
',max_file_size='.db_input($vars['max_file_size']).
',max_user_file_uploads='.db_input($vars['max_user_file_uploads']).
',max_staff_file_uploads='.db_input($vars['max_staff_file_uploads']).
',email_attachments='.db_input(isset($vars['email_attachments'])?1:0).
',allow_email_attachments='.db_input(isset($vars['allow_email_attachments'])?1:0).
',allow_online_attachments='.db_input(isset($vars['allow_online_attachments'])?1:0).
',allow_online_attachments_onlogin='.db_input(isset($vars['allow_online_attachments_onlogin'])?1:0).
' WHERE id='.db_input($this->getId());
return (db_query($sql));
}
function updateAutoresponderSetting($vars,&$errors) {
if($errors) return false;
$sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',ticket_autoresponder='.db_input($vars['ticket_autoresponder']).
',message_autoresponder='.db_input($vars['message_autoresponder']).
',ticket_notice_active='.db_input($vars['ticket_notice_active']).
',overlimit_notice_active='.db_input($vars['overlimit_notice_active']);
return (db_query($sql));
}
function updateKBSetting($vars,&$errors) {
if($errors) return false;
$sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',enable_kb='.db_input(isset($vars['enable_kb'])?1:0).
',enable_premade='.db_input(isset($vars['enable_premade'])?1:0);
return (db_query($sql));
}
function updateAlertsSetting($vars,&$errors) {
if($vars['ticket_alert_active']
&& (!isset($vars['ticket_alert_admin'])
&& !isset($vars['ticket_alert_dept_manager'])
&& !isset($vars['ticket_alert_dept_members']))) {
$errors['ticket_alert_active']='Select recipient(s)';
}
if($vars['message_alert_active']
&& (!isset($vars['message_alert_laststaff'])
&& !isset($vars['message_alert_assigned'])
&& !isset($vars['message_alert_dept_manager']))) {
$errors['message_alert_active']='Select recipient(s)';
}
if($vars['note_alert_active']
&& (!isset($vars['note_alert_laststaff'])
&& !isset($vars['note_alert_assigned'])
&& !isset($vars['note_alert_dept_manager']))) {
$errors['note_alert_active']='Select recipient(s)';
}
if($vars['transfer_alert_active']
&& (!isset($vars['transfer_alert_assigned'])
&& !isset($vars['transfer_alert_dept_manager'])
&& !isset($vars['transfer_alert_dept_members']))) {
$errors['transfer_alert_active']='Select recipient(s)';
}
if($vars['overdue_alert_active']
&& (!isset($vars['overdue_alert_assigned'])
&& !isset($vars['overdue_alert_dept_manager'])
&& !isset($vars['overdue_alert_dept_members']))) {
$errors['overdue_alert_active']='Select recipient(s)';
}
if($vars['assigned_alert_active']
&& (!isset($vars['assigned_alert_staff'])
&& !isset($vars['assigned_alert_team_lead'])
&& !isset($vars['assigned_alert_team_members']))) {
$errors['assigned_alert_active']='Select recipient(s)';
}
if($errors) return false;
$sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() '.
',ticket_notice_active='.db_input($vars['ticket_notice_active']).
',ticket_alert_active='.db_input($vars['ticket_alert_active']).
',ticket_alert_admin='.db_input(isset($vars['ticket_alert_admin'])?1:0).
',ticket_alert_dept_manager='.db_input(isset($vars['ticket_alert_dept_manager'])?1:0).
',ticket_alert_dept_members='.db_input(isset($vars['ticket_alert_dept_members'])?1:0).
',message_alert_active='.db_input($vars['message_alert_active']).
',message_alert_laststaff='.db_input(isset($vars['message_alert_laststaff'])?1:0).
',message_alert_assigned='.db_input(isset($vars['message_alert_assigned'])?1:0).
',message_alert_dept_manager='.db_input(isset($vars['message_alert_dept_manager'])?1:0).
',note_alert_active='.db_input($vars['note_alert_active']).
',note_alert_laststaff='.db_input(isset($vars['note_alert_laststaff'])?1:0).
',note_alert_assigned='.db_input(isset($vars['note_alert_assigned'])?1:0).
',note_alert_dept_manager='.db_input(isset($vars['note_alert_dept_manager'])?1:0).
',assigned_alert_active='.db_input($vars['assigned_alert_active']).
',assigned_alert_staff='.db_input(isset($vars['assigned_alert_staff'])?1:0).
',assigned_alert_team_lead='.db_input(isset($vars['assigned_alert_team_lead'])?1:0).
',assigned_alert_team_members='.db_input(isset($vars['assigned_alert_team_members'])?1:0).
',transfer_alert_active='.db_input($vars['transfer_alert_active']).
',transfer_alert_assigned='.db_input(isset($vars['transfer_alert_assigned'])?1:0).
',transfer_alert_dept_manager='.db_input(isset($vars['transfer_alert_dept_manager'])?1:0).
',transfer_alert_dept_members='.db_input(isset($var['transfer_alert_dept_members'])?1:0).
',overdue_alert_active='.db_input($vars['overdue_alert_active']).
',overdue_alert_assigned='.db_input(isset($vars['overdue_alert_assigned'])?1:0).
',overdue_alert_dept_manager='.db_input(isset($vars['overdue_alert_dept_manager'])?1:0).
',overdue_alert_dept_members='.db_input(isset($var['overdue_alert_dept_members'])?1:0).
',send_sys_errors='.db_input(isset($vars['send_sys_errors'])?1:0).
',send_sql_errors='.db_input(isset($vars['send_sql_errors'])?1:0).
',send_login_errors='.db_input(isset($vars['send_login_errors'])?1:0);
return (db_query($sql));
}
}
?>

43
include/class.cron.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
/*********************************************************************
class.cron.php
Nothing special...just a central location for all cron calls.
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
TODO: The plan is to make cron jobs db based.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
//TODO: Make it DB based!
class Cron {
function MailFetcher() {
require_once(INCLUDE_DIR.'class.mailfetch.php');
MailFetcher::fetchMail(); //Fetch mail..frequency is limited by email account setting.
}
function TicketMonitor() {
require_once(INCLUDE_DIR.'class.ticket.php');
require_once(INCLUDE_DIR.'class.lock.php');
Ticket::checkOverdue(); //Make stale tickets overdue
TicketLock::cleanup(); //Remove expired locks
}
function PurgeLogs() {
Sys::purgeLogs();
}
function run(){ //called by outside cron NOT autocron
Cron::MailFetcher();
Cron::TicketMonitor();
cron::PurgeLogs();
}
}
?>

318
include/class.dept.php Normal file
View File

@@ -0,0 +1,318 @@
<?php
/*********************************************************************
class.dept.php
Department class
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class Dept {
var $id;
var $email;
var $sla;
var $manager;
var $ht;
function Dept($id){
$this->id=0;
$this->load($id);
}
function load($id=0) {
global $cfg;
if(!$id && !($id=$this->getId()))
return false;
$sql='SELECT dept.*,dept.dept_id as id,dept.dept_name as name, dept.dept_signature as signature, count(staff.staff_id) as users '
.' FROM '.DEPT_TABLE.' dept '
.' LEFT JOIN '.STAFF_TABLE.' staff ON (dept.dept_id=staff.dept_id) '
.' WHERE dept.dept_id='.db_input($id)
.' GROUP BY dept.dept_id';
if(!($res=db_query($sql)) || !db_num_rows($res))
return false;
$this->ht=db_fetch_array($res);
$this->id=$this->ht['dept_id'];
$this->email=$this->sla=$this->manager=null;
$this->getEmail(); //Auto load email struct.
return true;
}
function reload(){
return $this->load();
}
function getId(){
return $this->id;
}
function getName(){
return $this->ht['name'];
}
function getEmailId(){
return $this->ht['email_id'];
}
function getEmail(){
if(!$this->email && $this->getEmailId())
$this->email=Email::lookup($this->getEmailId());
return $this->email;
}
function getNumStaff(){
return $this->ht['users'];
}
function getNumMembers(){
return $this->getNumStaff();
}
function getNumUsers(){
return $this->getNumStaff();
}
function getSLAId(){
return $this->ht['sla_id'];
}
function getSLA(){
if(!$this->sla && $this->getSLAId())
$this->sla=SLA::lookup($this->getSLAId());
return $this->sla;
}
function getTemplateId() {
return $this->ht['tpl_id'];
}
function getTemplate() {
if(!$this->template && $this->getTemplateId())
$this->template = Template::lookup($this->getTemplateId());
return $this->template;
}
function getAutoRespEmail() {
if(!$this->autorespEmail && $this->ht['autoresp_email_id'] && ($email=Email::lookup($this->ht['autoresp_email_id'])))
$this->autorespEmail=$email;
else // Defualt to dept email if autoresp is not specified or deleted.
$this->autorespEmail=$this->getEmail();
return $this->autorespEmail;
}
function getEmailAddress() {
if(($email=$this->getEmail()))
return $email->getAddress();
}
function getSignature() {
return $this->ht['signature'];
}
function canAppendSignature() {
return ($this->getSignature() && $this->isPublic());
}
function getManagerId(){
return $this->ht['manager_id'];
}
function getManager(){
if(!$this->manager && $this->getManagerId())
$this->manager=Staff::lookup($this->getManagerId());
return $this->manager;
}
function isPublic(){
return ($this->ht['ispublic']);
}
function autoRespONNewTicket(){
return ($this->ht['ticket_auto_response']);
}
function autoRespONNewMessage(){
return ($this->ht['message_auto_response']);
}
function noreplyAutoResp(){
return ($this->ht['noreply_autoresp']);
}
function getHashtable() {
return $this->ht;
}
function getInfo(){
return $this->getHashtable();
}
function update($vars,&$errors){
if($this->save($this->getId(),$vars,$errors)) {
$this->reload();
return true;
}
return false;
}
function delete() {
global $cfg;
if(!$cfg || $this->getId()==$cfg->getDefaultDeptId() || $this->getNumUsers())
return 0;
$id=$this->getId();
$sql='DELETE FROM '.DEPT_TABLE.' WHERE dept_id='.db_input($id).' LIMIT 1';
if(db_query($sql) && ($num=db_affected_rows())){
// DO SOME HOUSE CLEANING
//Move tickets to default Dept. TODO: Move one ticket at a time and send alerts + log notes.
db_query('UPDATE '.TICKET_TABLE.' SET dept_id='.db_input($cfg->getDefaultDeptId()).' WHERE dept_id='.db_input($id));
//Move Dept members: This should never happen..since delete should be issued only to empty Depts...but check it anyways
db_query('UPDATE '.STAFF_TABLE.' SET dept_id='.db_input($cfg->getDefaultDeptId()).' WHERE dept_id='.db_input($id));
//make help topic using the dept default to default-dept.
db_query('UPDATE '.TOPIC_TABLE.' SET dept_id='.db_input($cfg->getDefaultDeptId()).' WHERE dept_id='.db_input($id));
}
return $num;
}
/*----Static functions-------*/
function getIdByName($name) {
$id=0;
$sql ='SELECT dept_id FROM '.DEPT_TABLE.' WHERE dept_name='.db_input($name);
if(($res=db_query($sql)) && db_num_rows($res))
list($id)=db_fetch_row($res);
return $id;
}
function lookup($id){
return ($id && is_numeric($id) && ($dept = new Dept($id)) && $dept->getId()==$id)?$dept:null;
}
function getNameById($id) {
if($id && ($dept=Dept::lookup($id)))
$name= $dept->getName();
return $name;
}
function getDefaultDeptName() {
global $cfg;
return ($cfg && $cfg->getDefaultDeptId() && ($name=Dept::getNameById($cfg->getDefaultDeptId())))?$name:null;
}
function getDepartments( $publiconly=false) {
$depts=array();
$sql ='SELECT dept_id, dept_name FROM '.DEPT_TABLE;
if($publiconly)
$sql.=' WHERE ispublic=1';
if(($res=db_query($sql)) && db_num_rows($res)) {
while(list($id, $name)=db_fetch_row($res))
$depts[$id] = $name;
}
return $depts;
}
function getPublicDepartments() {
return self::getDepartments(true);
}
function create($vars,&$errors) {
return Dept::save(0,$vars,$errors);
}
function save($id,$vars,&$errors) {
global $cfg;
if($id && $id!=$vars['id'])
$errors['err']='Missing or invalid Dept ID (internal error).';
if(!$vars['email_id'] || !is_numeric($vars['email_id']))
$errors['email_id']='Email selection required';
if(!is_numeric($vars['tpl_id']))
$errors['tpl_id']='Template selection required';
if(!$vars['name']){
$errors['name']='Name required';
}elseif(strlen($vars['name'])<4) {
$errors['name']='Name is too short.';
}elseif(($did=Dept::getIdByName($vars['name'])) && $did!=$id){
$errors['name']='Department already exist';
}
if(!$vars['ispublic'] && ($vars['id']==$cfg->getDefaultDeptId()))
$errors['ispublic']='System default department can not be private';
if($errors) return false;
$sql='SET updated=NOW() '
.' ,ispublic='.db_input($vars['ispublic'])
.' ,email_id='.db_input($vars['email_id'])
.' ,tpl_id='.db_input($vars['tpl_id'])
.' ,sla_id='.db_input($vars['sla_id'])
.' ,autoresp_email_id='.db_input($vars['autoresp_email_id'])
.' ,manager_id='.db_input($vars['manager_id']?$vars['manager_id']:0)
.' ,dept_name='.db_input(Format::striptags($vars['name']))
.' ,dept_signature='.db_input(Format::striptags($vars['signature']))
.' ,ticket_auto_response='.db_input(isset($vars['ticket_auto_response'])?$vars['ticket_auto_response']:1)
.' ,message_auto_response='.db_input(isset($vars['message_auto_response'])?$vars['message_auto_response']:1);
if($id) {
$sql='UPDATE '.DEPT_TABLE.' '.$sql.' WHERE dept_id='.db_input($id);
if(db_query($sql) && db_affected_rows())
return true;
$errors['err']='Unable to update '.Format::htmlchars($vars['name']).' Dept. Error occurred';
}else{
$sql='INSERT INTO '.DEPT_TABLE.' '.$sql.',created=NOW()';
if(db_query($sql) && ($id=db_insert_id()))
return $id;
$errors['err']='Unable to create department. Internal error';
}
return false;
}
}
?>

View File

@@ -0,0 +1,189 @@
<?php
/*********************************************************************
class.dispatcher.php
Dispatcher that will read files with URL lists in them and attempt to
match the URL requested to a function that should be invoked to handle
the request.
Jared Hancock
Copyright (c) 2006-2012 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
/**
* URL resolver and dispatcher. It's meant to be quite lightweight, so the
* functions aren't separated
*/
class Dispatcher {
function Dispatcher($file=false) {
$this->urls = array();
$this->file = $file;
}
function resolve($url, $args=null) {
if ($this->file) { $this->lazy_load(); }
# Support HTTP method emulation with the _method GET argument
if (isset($_GET['_method'])) {
$_SERVER['REQUEST_METHOD'] = strtoupper($_GET['method']);
unset($_GET['_method']);
}
foreach ($this->urls as $matcher) {
if ($matcher->matches($url)) {
return $matcher->dispatch($url, $args);
}
}
Http::response(400, "URL not supported");
}
/**
* Returns the url for the given function and arguments (arguments
* aren't declared, but will be handled
*/
function reverse($func) { }
/**
* Add the url to the list of supported URLs
*/
function append($url, $prefix=false) {
if ($prefix) { $url->setPrefix($prefix); }
array_push($this->urls, $url);
}
/**
* Add the urls from another dispatcher onto this one
*/
function extend($dispatcher) {
foreach ($dispatcher->urls as $url) { $this->append($url); }
/* allow inlining / chaining */ return $this;
}
/* static */ function include_urls($file, $absolute=false, $lazy=true) {
if (!$absolute) {
# Fetch the working path of the caller
$bt = debug_backtrace();
$file = dirname($bt[0]["file"]) . "/" . $file;
}
if ($lazy) return new Dispatcher($file);
else return (include $file);
}
/**
* The include_urls() method will create a new Dispatcher and set the
* $this->file to where the file to be loaded is located. When this
* dispatcher is first accessed, the file will be loaded.
*/
function lazy_load() {
$this->extend(include $this->file);
$this->file=false;
}
}
class UrlMatcher {
function UrlMatcher($regex, $func, $args=false, $method=false) {
# Add the slashes for the Perl syntax
$this->regex = "@" . $regex . "@";
$this->func = $func;
$this->args = ($args) ? $args : array();
$this->prefix = false;
$this->method = $method;
}
function setPrefix($prefix) { $this->prefix = $prefix; }
function matches($url) {
if ($this->method && $_SERVER['REQUEST_METHOD'] != $this->method) {
return false;
}
return preg_match($this->regex, $url, $this->matches) == 1;
}
function dispatch($url, $prev_args=null) {
# Remove named values from the match array
$this->matches = array_flip(array_intersect(
array_flip($this->matches), range(0,31)));
if (@get_class($this->func) == "Dispatcher") {
# Trim the leading match off the $url and call the
# sub-dispatcher. This will be the case for lines in the URL
# file like
# url("^/blah", Dispatcher::include_urls("blah/urls.conf.php"))
# Also, pass arguments matched so far (if any) to the receiving
# resolve() method by merging the $prev_args into $this->matches
# (excluding $this->matches[0], which is the matched URL at this
# level)
return $this->func->resolve(
substr($url, strlen($this->matches[0])),
array_merge(($prev_args) ? $prev_args : array(),
array_slice($this->matches, 1)));
} else {
# Drop the first item of the matches array (which is the whole
# matched url). Then merge in any initial arguments.
array_shift($this->matches);
# Prepend received arguments (from a parent Dispatcher). This is
# different from the static args, which are postpended
if (is_array($prev_args))
$args = array_merge($prev_args, $this->matches);
else $args = $this->matches;
# Add in static args specified in the constructor
$args = array_merge($args, $this->args);
# Apply the $prefix given
list($class, $func) = $this->apply_prefix();
if ($class) {
# Create instance of the class, which is the first item,
# then call the method which is the second item
$func = array(new $class, $func);
}
if (!is_callable($func))
Http::response(500,
'Dispatcher compile error. Function not callable');
return call_user_func_array($func, $args);
}
}
/**
* For the $prefix recieved by the constuctor, prepend it to the
* received $class, if any, then make an import if necessary. Lastly,
* return the appropriate $class, and $func that should be invoked to
* dispatch the URL.
*/
function apply_prefix() {
if (is_array($this->func)) { list($class, $func) = $this->func; }
else { $func = $this->func; $class = ""; }
$class = $this->prefix . $class;
if (strpos($class, ":")) {
list($file, $class) = explode(":", $class, 2);
include $file;
}
return array($class, $func);
}
}
function patterns($prefix) {
$disp = new Dispatcher();
for ($i=1, $k=func_num_args(); $i<$k; $i++) {
# NOTE: that $prefix is added to each url rather than to the
# dispatcher as a whole so that urls can be copied from one
# dispatcher to another (via the ->extend() method) and
# completely maintain their integrity
$disp->append(func_get_arg($i), $prefix);
}
return $disp;
}
function url($regex, $func, $args=false, $method=false) {
return new UrlMatcher($regex, $func, $args, $method);
}
function url_post($regex, $func, $args=false) {
return url($regex, $func, $args, "POST");
}
function url_get($regex, $func, $args=false) {
return url($regex, $func, $args, "GET");
}
function url_del($regex, $func, $args=false) {
return url($regex, $func, $args, "DELETE");
}
?>

Some files were not shown because too many files have changed in this diff Show More