Move files into app subdir
authorBen Klang <ben@alkaloid.net>
Wed, 30 Dec 2009 23:02:27 +0000 (18:02 -0500)
committerBen Klang <ben@alkaloid.net>
Wed, 30 Dec 2009 23:02:27 +0000 (18:02 -0500)
92 files changed:
TODO [deleted file]
config/applist.xml.dist [deleted file]
config/conf.xml [deleted file]
devices.php [deleted file]
dialplan.php [deleted file]
dialplan/edit.php [deleted file]
extensions.php [deleted file]
index.php [deleted file]
lib/Dialplan.php [deleted file]
lib/Driver.php [deleted file]
lib/Driver/Ldap.php [deleted file]
lib/Driver/Sql.php [deleted file]
lib/Exception.php [deleted file]
lib/Forms/DeviceForm.php [deleted file]
lib/Forms/ExtensionForm.php [deleted file]
lib/Shout.php [deleted file]
lib/api.php [deleted file]
lib/base.php [deleted file]
lib/shoutAtts.php [deleted file]
lib/version.php [deleted file]
moh.php [deleted file]
security.php [deleted file]
shout/TODO [new file with mode: 0644]
shout/config/applist.xml.dist [new file with mode: 0644]
shout/config/conf.xml [new file with mode: 0644]
shout/devices.php [new file with mode: 0644]
shout/dialplan.php [new file with mode: 0644]
shout/dialplan/edit.php [new file with mode: 0644]
shout/extensions.php [new file with mode: 0644]
shout/index.php [new file with mode: 0644]
shout/lib/Dialplan.php [new file with mode: 0644]
shout/lib/Driver.php [new file with mode: 0644]
shout/lib/Driver/Ldap.php [new file with mode: 0644]
shout/lib/Driver/Sql.php [new file with mode: 0644]
shout/lib/Exception.php [new file with mode: 0644]
shout/lib/Forms/DeviceForm.php [new file with mode: 0644]
shout/lib/Forms/ExtensionForm.php [new file with mode: 0644]
shout/lib/Shout.php [new file with mode: 0644]
shout/lib/api.php [new file with mode: 0644]
shout/lib/base.php [new file with mode: 0644]
shout/lib/shoutAtts.php [new file with mode: 0644]
shout/lib/version.php [new file with mode: 0644]
shout/moh.php [new file with mode: 0644]
shout/security.php [new file with mode: 0644]
shout/templates/common-header.inc [new file with mode: 0644]
shout/templates/content_page [new file with mode: 0644]
shout/templates/context/contextline.inc [new file with mode: 0644]
shout/templates/devices/edit.inc [new file with mode: 0644]
shout/templates/devices/list.inc [new file with mode: 0644]
shout/templates/dialplan/contexttree.inc [new file with mode: 0644]
shout/templates/dialplan/dialplanlist.inc [new file with mode: 0644]
shout/templates/dialplan/extensiondetail.inc [new file with mode: 0644]
shout/templates/dialplan/manager.inc [new file with mode: 0644]
shout/templates/dialplan/priority-form-begin.inc [new file with mode: 0644]
shout/templates/dialplan/priority-form-end.inc [new file with mode: 0644]
shout/templates/dialplan/priority-form-line.inc [new file with mode: 0644]
shout/templates/extensions/edit.inc [new file with mode: 0644]
shout/templates/extensions/list.inc [new file with mode: 0644]
shout/templates/javascript/dialplan.js [new file with mode: 0644]
shout/templates/menu.inc [new file with mode: 0644]
shout/templates/table-limiter-begin.inc [new file with mode: 0644]
shout/templates/table-limiter-end.inc [new file with mode: 0644]
shout/themes/graphics/add-extension.gif [new file with mode: 0644]
shout/themes/graphics/add-user.gif [new file with mode: 0644]
shout/themes/graphics/shout.png [new file with mode: 0644]
shout/themes/graphics/telephone-pole.png [new file with mode: 0644]
shout/themes/graphics/user.png [new file with mode: 0644]
shout/themes/screen.css [new file with mode: 0644]
templates/common-header.inc [deleted file]
templates/content_page [deleted file]
templates/context/contextline.inc [deleted file]
templates/devices/edit.inc [deleted file]
templates/devices/list.inc [deleted file]
templates/dialplan/contexttree.inc [deleted file]
templates/dialplan/dialplanlist.inc [deleted file]
templates/dialplan/extensiondetail.inc [deleted file]
templates/dialplan/manager.inc [deleted file]
templates/dialplan/priority-form-begin.inc [deleted file]
templates/dialplan/priority-form-end.inc [deleted file]
templates/dialplan/priority-form-line.inc [deleted file]
templates/extensions/edit.inc [deleted file]
templates/extensions/list.inc [deleted file]
templates/javascript/dialplan.js [deleted file]
templates/menu.inc [deleted file]
templates/table-limiter-begin.inc [deleted file]
templates/table-limiter-end.inc [deleted file]
themes/graphics/add-extension.gif [deleted file]
themes/graphics/add-user.gif [deleted file]
themes/graphics/shout.png [deleted file]
themes/graphics/telephone-pole.png [deleted file]
themes/graphics/user.png [deleted file]
themes/screen.css [deleted file]

diff --git a/TODO b/TODO
deleted file mode 100644 (file)
index c53e828..0000000
--- a/TODO
+++ /dev/null
@@ -1 +0,0 @@
-* Convert forms to Beatnik style associative arrays. 2006-01-26 bklang
diff --git a/config/applist.xml.dist b/config/applist.xml.dist
deleted file mode 100644 (file)
index 07f72e4..0000000
+++ /dev/null
@@ -1,862 +0,0 @@
-<?xml version="1.0"?>
-<asterisk type="applist">    <application name="SayAlpha">
-        <synopsis>Say Alpha</synopsis>
-        <usage>  SayAlpha(string): Spells the passed string
-
-</usage>
-    </application>
-    <application name="TxFAX">
-        <synopsis>Send a FAX file</synopsis>
-        <usage>  TxFAX(filename[|option]):  Send a given file name as a FAX. Returns -1
-Uses LOCALSTATIONID to identify itself to the remote end.
-Sets REMOTESTATIONID to the receiver CSID.
-Returns -1 when the user hangs up, or if the file does not exist.
-Returns 0 otherwise.
-
-</usage>
-    </application>
-    <application name="VoiceMailMain">
-        <synopsis>Enter voicemail system</synopsis>
-        <usage>  VoiceMailMain([[s]mailbox][@context]): Enters the main voicemail system
-for the checking of voicemail.  The mailbox can be passed as the option,
-which will stop the voicemail system from prompting the user for the mailbox.
-If the mailbox is preceded by &#39;s&#39; then the password check will be skipped.  If
-the mailbox is preceded by &#39;p&#39; then the supplied mailbox is prepended to the
-user&#39;s entry and the resulting string is used as the mailbox number.  This is
-useful for virtual hosting of voicemail boxes.  If a context is specified,
-logins are considered in that voicemail context only.
-Returns -1 if the user hangs up or 0 otherwise.
-
-</usage>
-    </application>
-    <application name="StripLSD">
-        <synopsis>Strip Least Significant Digits</synopsis>
-        <usage>  StripLSD(count): Strips the trailing  &#39;count&#39;  digits  from  the  channel&#39;s
-associated extension. For example, the  number  5551212 when stripped with a
-count of 4 would be changed to 555.  This app always returns 0, and the PBX
-will continue processing at the next priority for the *new* extension.
-  So, for  example, if  priority 3 of 5551212  is  StripLSD 4, the next step
-executed will be priority 4 of 555.  If you switch into an  extension which
-has no first step, the PBX will treat it as though the user dialed an
-invalid extension.
-
-</usage>
-    </application>
-    <application name="ChangeMonitor">
-        <synopsis>Change monitoring filename of a channel</synopsis>
-        <usage>ChangeMonitor(filename_base)
-Changes monitoring filename of a channel. Has no effect if the channel is not monitored
-The argument is the new filename base to use for monitoring this channel.
-
-</usage>
-    </application>
-    <application name="BackgroundDetect">
-        <synopsis>Background a file with talk detect</synopsis>
-        <usage>  BackgroundDetect(filename[|sil[|min|[max]]]):  Plays  back  a  given
-filename, waiting for interruption from a given digit (the digit must
-start the beginning of a valid extension, or it will be ignored).
-During the playback of the file, audio is monitored in the receive
-direction, and if a period of non-silence which is greater than &#39;min&#39; ms
-yet less than &#39;max&#39; ms is followed by silence for at least &#39;sil&#39; ms then
-the audio playback is aborted and processing jumps to the &#39;talk&#39; extension
-if available.  If unspecified, sil, min, and max default to 1000, 100, and
-infinity respectively.  Returns -1 on hangup, and 0 on successful playback
-completion with no exit conditions.
-
-</usage>
-    </application>
-    <application name="SayNumber">
-        <synopsis>Say Number</synopsis>
-        <usage>  SayNumber(digits[,gender]): Says the passed number. SayNumber is using
-the current language setting for the channel. (See app SetLanguage).
-
-</usage>
-    </application>
-    <application name="Directory">
-        <synopsis>Provide directory of voicemail extensions</synopsis>
-        <usage>  Directory(vm-context[|dial-context[|options]]): Presents the user with a directory
-of extensions from which they  may  select  by name. The  list  of  names 
-and  extensions  is discovered from  voicemail.conf. The  vm-context  argument
-is required, and specifies  the  context  of voicemail.conf to use.  The
-dial-context is the context to use for dialing the users, and defaults to
-the vm-context if unspecified. The &#39;f&#39; option causes the directory to match
-based on the first name in voicemail.conf instead of the last name.
-Returns 0 unless the user hangs up. It  also sets up the channel on exit
-to enter the extension the user selected.
-
-</usage>
-    </application>
-    <application name="SetCallerID">
-        <synopsis>Set CallerID</synopsis>
-        <usage>  SetCallerID(clid[|a]): Set Caller*ID on a call to a new
-value.  Sets ANI as well if a flag is used.  Always returns 0
-
-</usage>
-    </application>
-    <application name="Congestion">
-        <synopsis>Indicate congestion and stop</synopsis>
-        <usage>  Congestion([timeout]): Requests that the channel indicate congestion
-and then waits for the user to hang up or for the optional timeout to
-expire.  Always returns -1.
-</usage>
-    </application>
-    <application name="Busy">
-        <synopsis>Indicate busy condition and stop</synopsis>
-        <usage>  Busy([timeout]): Requests that the channel indicate busy condition and
-then waits for the user to hang up or the optional timeout to expire.
-Always returns -1.
-</usage>
-    </application>
-    <application name="Park">
-        <synopsis>Park yourself</synopsis>
-        <usage>Park(exten):Used to park yourself (typically in combination with a supervised
-transfer to know the parking space.  This Application is always
-registered internally and does not need to be explicitly added
-into the dialplan, although you should include the &#39;parkedcalls&#39;
-context.
-
-</usage>
-    </application>
-    <application name="VoiceMail">
-        <synopsis>Leave a voicemail message</synopsis>
-        <usage>  VoiceMail([s|u|b]extension[@context][&amp;extension[@context]][...]):  Leavesvoicemail for a given extension (must be configured in voicemail.conf).
- If the extension is preceded by 
-* &#39;s&#39; then instructions for leaving the message will be skipped.
-* &#39;u&#39; then the &quot;unavailable&quot; message will be played.
-  (/var/lib/asterisk/sounds/vm/&lt;exten&gt;/unavail) if it exists.
-* &#39;b&#39; then the the busy message will be played (that is, busy instead of unavail).
-If the caller presses &#39;0&#39; (zero) during the prompt, the call jumps to
-extension &#39;o&#39; in the current context.
-If the caller presses &#39;*&#39; during the prompt, the call jumps to
-extension &#39;a&#39; in the current context.
-If the requested mailbox does not exist, and there exists a priority
-n + 101, then that priority will be taken next.
-When multiple mailboxes are specified, the unavailable or busy message
-will be taken from the first mailbox specified.
-Returns -1 on error or mailbox not found, or if the user hangs up.
-Otherwise, it returns 0.
-
-</usage>
-    </application>
-    <application name="Wait">
-        <synopsis>Waits for some time</synopsis>
-        <usage>  Wait(seconds): Waits for a specified number of seconds, then returns 0.
-seconds can be passed with fractions of a second. (eg: 1.5 = 1.5 seconds)
-
-</usage>
-    </application>
-    <application name="CallingPres">
-        <synopsis>Change the presentation for the callerid</synopsis>
-        <usage>Callingpres(number): Changes the presentation for the callerid. Should be called before placing an outgoing call
-
-</usage>
-    </application>
-    <application name="MusicOnHold">
-        <synopsis>Play Music On Hold indefinitely</synopsis>
-        <usage>MusicOnHold(class): Plays hold music specified by class.  If omitted, the default
-music source for the channel will be used. Set the default 
-class with the SetMusicOnHold() application.
-Returns -1 on hangup.
-Never returns otherwise.
-
-</usage>
-    </application>
-    <application name="AbsoluteTimeout">
-        <synopsis>Set absolute maximum time of call</synopsis>
-        <usage>  AbsoluteTimeout(seconds): Set the absolute maximum amount of time permitted
-for a call.  A setting of 0 disables the timeout.  Always returns 0.
-
-</usage>
-    </application>
-    <application name="VoiceMailMain2">
-        <synopsis>Enter voicemail system</synopsis>
-        <usage>  VoiceMailMain([[s]mailbox][@context]): Enters the main voicemail system
-for the checking of voicemail.  The mailbox can be passed as the option,
-which will stop the voicemail system from prompting the user for the mailbox.
-If the mailbox is preceded by &#39;s&#39; then the password check will be skipped.  If
-the mailbox is preceded by &#39;p&#39; then the supplied mailbox is prepended to the
-user&#39;s entry and the resulting string is used as the mailbox number.  This is
-useful for virtual hosting of voicemail boxes.  If a context is specified,
-logins are considered in that voicemail context only.
-Returns -1 if the user hangs up or 0 otherwise.
-
-</usage>
-    </application>
-    <application name="BackGround">
-        <synopsis>Play a file while awaiting extension</synopsis>
-        <usage>  Background(filename[|options[|langoverride]]): Plays a given file, while simultaneously
-waiting for the user to begin typing an extension. The  timeouts do not
-count until the last BackGround application has ended.
-Options may also be  included following a pipe symbol. The &#39;skip&#39;
-option causes the playback of the message to  be  skipped  if  the  channel
-is not in the &#39;up&#39; state (i.e. it hasn&#39;t been  answered  yet. If &#39;skip&#39; is 
-specified, the application will return immediately should the channel not be
-off hook.  Otherwise, unless &#39;noanswer&#39; is specified, the channel channel will
-be answered before the sound is played. Not all channels support playing
-messages while still hook. The &#39;langoverride&#39; may be a language to use for
-playing the prompt which differs from the current language of the channel
-Returns -1 if the channel was hung up, or if the file does not exist. 
-Returns 0 otherwise.
-
-</usage>
-    </application>
-    <application name="Verbose">
-        <synopsis>Send arbitrary text to verbose output</synopsis>
-        <usage>Verbose([&lt;level&gt;|]&lt;message&gt;)
-  level must be an integer value.  If not specified, defaults to 0.  Always returns 0.
-
-</usage>
-    </application>
-    <application name="StopMonitor">
-        <synopsis>Stop monitoring a channel</synopsis>
-        <usage>StopMonitor
-Stops monitoring a channel. Has no effect if the channel is not monitored
-
-</usage>
-    </application>
-    <application name="SubString">
-        <synopsis>(Deprecated) Save substring digits in a given variable</synopsis>
-        <usage>  (Deprecated, use ${variable:a:b} instead)
-
-  SubString(variable=string_of_digits|count1|count2): Assigns the substring
-of string_of_digits to a given variable. Parameter count1 may be positive
-or negative. If it&#39;s positive then we skip the first count1 digits from the
-left. If it&#39;s negative, we move count1 digits counting from the end of
-the string to the left. Parameter count2 implies how many digits we are
-taking from the point that count1 placed us. If count2 is negative, then
-that many digits are omitted from the end.
-For example:
-exten =&gt; _NXXXXXX,1,SubString,test=2564286161|0|3
-assigns the area code (3 first digits) to variable test.
-exten =&gt; _NXXXXXX,1,SubString,test=2564286161|-7|7
-assigns the last 7 digits to variable test.
-exten =&gt; _NXXXXXX,1,SubString,test=2564286161|0|-4
-assigns all but the last 4 digits to variable test.
-If there are no parameters it&#39;ll return with -1.
-If there wrong parameters it go on and return with 0
-
-</usage>
-    </application>
-    <application name="MeetMeCount">
-        <synopsis>MeetMe participant count</synopsis>
-        <usage>  MeetMeCount(confno[|var]): Plays back the number of users in the specifiedi
-MeetMe conference. If var is specified, playback will be skipped and the value
-will be returned in the variable. Returns 0 on success or -1 on a hangup.
-A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.
-
-</usage>
-    </application>
-    <application name="SetCIDName">
-        <synopsis>Set CallerID Name</synopsis>
-        <usage>  SetCIDName(cname[|a]): Set Caller*ID Name on a call to a new
-value, while preserving the original Caller*ID number.  This is
-useful for providing additional information to the called
-party. Sets ANI as well if a flag is used.  Always returns 0
-
-</usage>
-    </application>
-    <application name="Flash">
-        <synopsis>Flashes a Zap Trunk</synopsis>
-        <usage>  Flash(): Sends a flash on a zap trunk.  This is only a hack for
-people who want to perform transfers and such via AGI and is generally
-quite useless otherwise.  Returns 0 on success or -1 if this is not
-a zap trunk
-
-</usage>
-    </application>
-    <application name="Suffix">
-        <synopsis>Append trailing digits</synopsis>
-        <usage>  Suffix(digits): Appends the  digit  string  specified  by  digits to the
-channel&#39;s associated extension. For example, the number 555 when  suffixed
-with &#39;1212&#39; will become 5551212. This app always returns 0, and the PBX will
-continue processing at the next priority for the *new* extension.
-  So, for example, if priority  3  of  555 is Suffix 1212, the  next  step
-executed will be priority 4 of 5551212. If  you  switch  into an  extension
-which has no first step, the PBX will treat it as though the user dialed an
-invalid extension.
-
-</usage>
-    </application>
-    <application name="SetAccount">
-        <synopsis>Sets account code</synopsis>
-        <usage>  SetAccount([account]):  Set  the  channel account code for billing
-purposes. Always returns 0.
-
-</usage>
-    </application>
-    <application name="SayPhonetic">
-        <synopsis>Say Phonetic</synopsis>
-        <usage>  SayPhonetic(string): Spells the passed string with phonetic alphabet
-
-</usage>
-    </application>
-    <application name="Monitor">
-        <synopsis>Monitor a channel</synopsis>
-        <usage>Monitor([file_format|[fname_base]|[options]]):
-Used to start monitoring a channel. The channel&#39;s input and output
-voice packets are logged to files until the channel hangs up or
-monitoring is stopped by the StopMonitor application.
-  file_format          optional, if not set, defaults to &quot;wav&quot;
-  fname_base           if set, changes the filename used to the one specified.
-  options:
-    m   - when the recording ends mix the two leg files into one and
-          delete the two leg files.  If the variable MONITOR_EXEC is set, the
-          application referenced in it will be executed instead of
-          soxmix and the raw leg files will NOT be deleted automatically.
-          soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files
-          and a target mixed file name which is the same as the leg file names
-          only without the in/out designator.
-          If MONITOR_EXEC_ARGS is set, the contents will be passed on as
-          additional arguements to MONITOR_EXEC
-          Both MONITOR_EXEC and the Mix flag can be set from the
-          administrator interface
-
-    b   - Don&#39;t begin recording unless a call is bridged to another channel
-
-Returns -1 if monitor files can&#39;t be opened or if the channel is already
-monitored, otherwise 0.
-
-</usage>
-    </application>
-    <application name="WaitMusicOnHold">
-        <synopsis>Wait, playing Music On Hold</synopsis>
-        <usage>WaitMusicOnHold(delay): Plays hold music specified number of seconds.  Returns 0 when
-done, or -1 on hangup.  If no hold music is available, the delay will
-still occur with no sound.
-
-</usage>
-    </application>
-    <application name="Answer">
-        <synopsis>Answer a channel if ringing</synopsis>
-        <usage>  Answer(): If the channel is ringing, answer it, otherwise do nothing. 
-Returns 0 unless it tries to answer the channel and fails.
-
-</usage>
-    </application>
-    <application name="Echo">
-        <synopsis>Echo audio read back to the user</synopsis>
-        <usage>  Echo():  Echo audio read from channel back to the channel. Returns 0
-if the user exits with the &#39;#&#39; key, or -1 if the user hangs up.
-
-</usage>
-    </application>
-    <application name="SayDigits">
-        <synopsis>Say Digits</synopsis>
-        <usage>  SayDigits(digits): Says the passed digits. SayDigits is using the
-current language setting for the channel. (See app setLanguage)
-
-</usage>
-    </application>
-    <application name="MailboxExists">
-        <synopsis>Check if vmbox exists</synopsis>
-        <usage>  MailboxExists(mailbox[@context]): Conditionally branches to priority n+101
-if the specified voice mailbox exists.
-
-</usage>
-    </application>
-    <application name="Goto">
-        <synopsis>Goto a particular priority, extension, or context</synopsis>
-        <usage>  Goto([[context|]extension|]priority):  Set the  priority to the specified
-value, optionally setting the extension and optionally the context as well.
-The extension BYEXTENSION is special in that it uses the current extension,
-thus  permitting you to go to a different context, without specifying a
-specific extension. Always returns 0, even if the given context, extension,
-or priority is invalid.
-
-</usage>
-    </application>
-    <application name="MeetMe">
-        <synopsis>MeetMe conference bridge</synopsis>
-        <usage>  MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe conference.
-If the conference number is omitted, the user will be prompted to enter
-one. 
-MeetMe returns 0 if user pressed # to exit (see option &#39;p&#39;), otherwise -1.
-Please note: A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING TO WORK!
-
-The option string may contain zero or more of the following characters:
-      &#39;m&#39; -- set monitor only mode (Listen only, no talking)
-      &#39;t&#39; -- set talk only mode. (Talk only, no listening)
-      &#39;p&#39; -- allow user to exit the conference by pressing &#39;#&#39;
-      &#39;X&#39; -- allow user to exit the conference by entering a valid single
-             digit extension ${MEETME_EXIT_CONTEXT} or the current context
-             if that variable is not defined.
-      &#39;d&#39; -- dynamically add conference
-      &#39;D&#39; -- dynamically add conference, prompting for a PIN
-      &#39;e&#39; -- select an empty conference
-      &#39;E&#39; -- select an empty pinless conference
-      &#39;v&#39; -- video mode
-      &#39;q&#39; -- quiet mode (don&#39;t play enter/leave sounds)
-      &#39;M&#39; -- enable music on hold when the conference has a single caller
-      &#39;x&#39; -- close the conference when last marked user exits
-      &#39;w&#39; -- wait until the marked user enters the conference
-      &#39;b&#39; -- run AGI script specified in ${MEETME_AGI_BACKGROUND}
-         Default: conf-background.agi
-        (Note: This does not work with non-Zap channels in the same conference)
-      &#39;s&#39; -- Present menu (user or admin) when &#39;*&#39; is received (&#39;send&#39; to menu)
-      &#39;a&#39; -- set admin mode
-      &#39;A&#39; -- set marked mode
-
-</usage>
-    </application>
-    <application name="SetGlobalVar">
-        <synopsis>Set global variable to value</synopsis>
-        <usage>  SetGlobalVar(#n=value): Sets global variable n to value. Global
-variable are available across channels.
-
-</usage>
-    </application>
-    <application name="SoftHangup">
-        <synopsis>Soft Hangup Application</synopsis>
-        <usage>  SoftHangup(Technology/resource)
-Hangs up the requested channel.  Always returns 0
-
-</usage>
-    </application>
-    <application name="SetCDRUserField">
-        <synopsis>Set the CDR user field</synopsis>
-        <usage>[Synopsis]
-SetCDRUserField(value)
-
-[Description]
-SetCDRUserField(value): Set the CDR &#39;user field&#39; to value
-       The Call Data Record (CDR) user field is an extra field you
-       can use for data not stored anywhere else in the record.
-       CDR records can be used for billing or storing other arbitrary data
-       (I.E. telephone survey responses)
-       Also see AppendCDRUserField().
-       Always returns 0
-
-</usage>
-    </application>
-    <application name="Ringing">
-        <synopsis>Indicate ringing tone</synopsis>
-        <usage>  Ringing(): Request that the channel indicate ringing tone to the user.
-Always returns 0.
-
-</usage>
-    </application>
-    <application name="System">
-        <synopsis>Execute a system command</synopsis>
-        <usage>  System(command): Executes a command  by  using  system(). Returns -1 on
-failure to execute the specified command. If  the command itself executes
-but is in error, and if there exists a priority n + 101, where &#39;n&#39; is the
-priority of the current instance, then  the  channel  will  be  setup  to
-continue at that priority level.  Otherwise, System returns 0.
-
-</usage>
-    </application>
-    <application name="VoiceMail2">
-        <synopsis>Leave a voicemail message</synopsis>
-        <usage>  VoiceMail([s|u|b]extension[@context][&amp;extension[@context]][...]):  Leavesvoicemail for a given extension (must be configured in voicemail.conf).
- If the extension is preceded by 
-* &#39;s&#39; then instructions for leaving the message will be skipped.
-* &#39;u&#39; then the &quot;unavailable&quot; message will be played.
-  (/var/lib/asterisk/sounds/vm/&lt;exten&gt;/unavail) if it exists.
-* &#39;b&#39; then the the busy message will be played (that is, busy instead of unavail).
-If the caller presses &#39;0&#39; (zero) during the prompt, the call jumps to
-extension &#39;o&#39; in the current context.
-If the caller presses &#39;*&#39; during the prompt, the call jumps to
-extension &#39;a&#39; in the current context.
-If the requested mailbox does not exist, and there exists a priority
-n + 101, then that priority will be taken next.
-When multiple mailboxes are specified, the unavailable or busy message
-will be taken from the first mailbox specified.
-Returns -1 on error or mailbox not found, or if the user hangs up.
-Otherwise, it returns 0.
-
-</usage>
-    </application>
-    <application name="DigitTimeout">
-        <synopsis>Set maximum timeout between digits</synopsis>
-        <usage>  DigitTimeout(seconds): Set the maximum amount of time permitted between
-digits when the user is typing in an extension. When this timeout expires,
-after the user has started to type in an extension, the extension will be
-considered complete, and will be interpreted. Note that if an extension
-typed in is valid, it will not have to timeout to be tested, so typically
-at the expiry of this timeout, the extension will be considered invalid
-(and thus control would be passed to the &#39;i&#39; extension, or if it doesn&#39;t
-exist the call would be terminated). The default timeout is 5 seconds.
-Always returns 0.
-
-</usage>
-    </application>
-    <application name="ZapScan">
-        <synopsis>Scan Zap channels to monitor calls</synopsis>
-        <usage>  ZapScan allows a call center manager to monitor Zap channels in
-a convenient way.  Use &#39;#&#39; to select the next channel and use &#39;*&#39; to exit
-
-</usage>
-    </application>
-    <application name="TrySystem">
-        <synopsis>Try executing a system command</synopsis>
-        <usage>  TrySystem(command): Executes a command  by  using  system(). Returns 0
-on any situation. If  the command itself executes but is in error, and if
-there exists a priority n + 101, where &#39;n&#39; is the priority of the current
-instance, then  the  channel  will  be  setup  to continue at that
-priority level.  Otherwise, System returns 0.
-
-</usage>
-    </application>
-    <application name="SetAMAFlags">
-        <synopsis>Sets AMA Flags</synopsis>
-        <usage>  SetAMAFlags([flag]):  Set  the  channel AMA Flags for billing
-purposes. Always returns 0.
-
-</usage>
-    </application>
-    <application name="WaitForRing">
-        <synopsis>Wait for Ring Application</synopsis>
-        <usage>  WaitForRing(timeout)
-Returns 0 after waiting at least timeout seconds. and
-only after the next ring has completed.  Returns 0 on
-success or -1 on hangup
-
-</usage>
-    </application>
-    <application name="Prefix">
-        <synopsis>Prepend leading digits</synopsis>
-        <usage>  Prefix(digits): Prepends the digit string specified by digits to the
-channel&#39;s associated extension. For example, the number 1212 when prefixed
-with &#39;555&#39; will become 5551212. This app always returns 0, and the PBX will
-continue processing at the next priority for the *new* extension.
-  So, for example, if priority  3  of 1212 is  Prefix  555, the next step
-executed will be priority 4 of 5551212. If you switch into an extension
-which has no first step, the PBX will treat it as though the user dialed an
-invalid extension.
-
-</usage>
-    </application>
-    <application name="AppendCDRUserField">
-        <synopsis>Append to the CDR user field</synopsis>
-        <usage>[Synopsis]
-AppendCDRUserField(value)
-
-[Description]
-AppendCDRUserField(value): Append value to the CDR user field
-       The Call Data Record (CDR) user field is an extra field you
-       can use for data not stored anywhere else in the record.
-       CDR records can be used for billing or storing other arbitrary data
-       (I.E. telephone survey responses)
-       Also see SetCDRUserField().
-       Always returns 0
-
-</usage>
-    </application>
-    <application name="TXTCIDName">
-        <synopsis>Lookup caller name from TXT record</synopsis>
-        <usage>  TXTLookup(CallerID):  Looks up a Caller Name via DNS and sets
-the variable &#39;TXTCIDNAME&#39;. TXTCIDName will either be blank
-or return the value found in the TXT record in DNS.
-
-</usage>
-    </application>
-    <application name="RxFAX">
-        <synopsis>Receive a FAX to a file</synopsis>
-        <usage>  RxFAX(filename): Receives a FAX from the channel into a
-given filename. If the file exists it will be overwritten. The file
-should be in TIFF/F format.
-Uses LOCALSTATIONID to identify itself to the remote end.
-     LOCALHEADERINFO to generate a header line on each page.
-Sets REMOTESTATIONID to the sender CSID.
-     FAXPAGES to the number of pages received.
-     FAXBITRATE to the transmition rate.
-     FAXRESOLUTION to the resolution.
-Returns -1 when the user hangs up.
-
-</usage>
-    </application>
-    <application name="Transfer">
-        <synopsis>Transfer caller to remote extension</synopsis>
-        <usage>  Transfer(exten):  Requests the remote caller be transferred to
-a given extension. Returns -1 on hangup, or 0 on completion
-regardless of whether the transfer was successful.  If the transfer
-was *not* supported or successful and there exists a priority n + 101,
-then that priority will be taken next.
-
-</usage>
-    </application>
-    <application name="SetMusicOnHold">
-        <synopsis>Set default Music On Hold class</synopsis>
-        <usage>SetMusicOnHold(class): Sets the default class for music on hold for a given channel.  When
-music on hold is activated, this class will be used to select which
-music is played.
-
-</usage>
-    </application>
-    <application name="MeetMeAdmin">
-        <synopsis>MeetMe conference Administration</synopsis>
-        <usage>  MeetMeAdmin(confno,command[,user]): Run admin command for conference
-      &#39;K&#39; -- Kick all users out of conference
-      &#39;k&#39; -- Kick one user out of conference
-      &#39;L&#39; -- Lock conference
-      &#39;l&#39; -- Unlock conference
-      &#39;M&#39; -- Mute conference
-      &#39;m&#39; -- Unmute conference
-
-</usage>
-    </application>
-    <application name="SetVar">
-        <synopsis>Set variable to value</synopsis>
-        <usage>  Setvar(#n=value): Sets channel specific variable n to value
-</usage>
-    </application>
-    <application name="Record">
-        <synopsis>Record to a file</synopsis>
-        <usage>  Record(filename:format|silence[|maxduration][|option])
-
-Records from the channel into a given filename. If the file exists it will
-be overwritten.
-- &#39;format&#39; is the format of the file type to be recorded (wav, gsm, etc).
-- &#39;silence&#39; is the number of seconds of silence to allow before returning.
-- &#39;maxduration&#39; is the maximum recording duration in seconds. If missing
-or 0 there is no maximum.
-- &#39;option&#39; may be &#39;skip&#39; to return immediately if the line is not up,
-or &#39;noanswer&#39; to attempt to record even if the line is not up.
-
-If filename contains &#39;%d&#39;, these characters will be replaced with a number
-incremented by one each time the file is recorded. 
-
-Formats: g723, g729, gsm, h263, ulaw, alaw, vox, wav, WAV
-
-User can press &#39;#&#39; to terminate the recording and continue to the next priority.
-
-Returns -1 when the user hangs up.
-
-</usage>
-    </application>
-    <application name="SetCIDNum">
-        <synopsis>Set CallerID Number</synopsis>
-        <usage>  SetCIDNum(cnum[|a]): Set Caller*ID Number on a call to a new
-value, while preserving the original Caller*ID name.  This is
-useful for providing additional information to the called
-party. Sets ANI as well if a flag is used.  Always returns 0
-
-</usage>
-    </application>
-    <application name="ResetCDR">
-        <synopsis>Resets the Call Data Record</synopsis>
-        <usage>  ResetCDR([options]):  Causes the Call Data Record to be reset, optionally
-storing the current CDR before zeroing it out (if &#39;w&#39; option is specifed).
-record WILL be stored.
-Always returns 0.
-
-</usage>
-    </application>
-    <application name="Playback">
-        <synopsis>Play a file</synopsis>
-        <usage>  Playback(filename[|option]):  Plays  back  a  given  filename (do not put
-extension). Options may also be  included following a pipe symbol. The &#39;skip&#39;
-option causes the playback of the message to  be  skipped  if  the  channel
-is not in the &#39;up&#39; state (i.e. it hasn&#39;t been  answered  yet. If &#39;skip&#39; is 
-specified, the application will return immediately should the channel not be
-off hook.  Otherwise, unless &#39;noanswer&#39; is specified, the channel channel will
-be answered before the sound is played. Not all channels support playing
-messages while still hook. Returns -1 if the channel was hung up, or if the
-file does not exist. Returns 0 otherwise.
-
-</usage>
-    </application>
-    <application name="Macro">
-        <synopsis>Macro Implementation</synopsis>
-        <usage>  Macro(macroname|arg1|arg2...): Executes a macro using the context
-&#39;macro-&lt;macroname&gt;&#39;, jumping to the &#39;s&#39; extension of that context and
-executing each step, then returning when the steps end.  The calling
-extension, context, and priority are stored in ${MACRO_EXTEN}, 
-${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become
-${ARG1}, ${ARG2}, etc in the macro context.  Macro returns -1 if
-any step in the macro returns -1, and 0 otherwise.  If you Goto out
-of the Macro context, the Macro will terminate and control will be return
-at the location of the Goto.  Otherwise if ${MACRO_OFFSET} is set at
-termination, Macro will attempt to continue at priority
-MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.
-
-</usage>
-    </application>
-    <application name="SIPDtmfMode">
-        <synopsis>Change the dtmfmode for a SIP call</synopsis>
-        <usage>SIPDtmfMode(inband|info|rfc2833): Changes the dtmfmode for a SIP call
-
-</usage>
-    </application>
-    <application name="WaitExten">
-        <synopsis>Waits for some time</synopsis>
-        <usage>  Wait(seconds): Waits for the user to enter a new extension for the 
-specified number of seconds, then returns 0.  Seconds can be passed with
-fractions of a second. (eg: 1.5 = 1.5 seconds)
-
-</usage>
-    </application>
-    <application name="Dial">
-        <synopsis>Place a call and connect to the current channel</synopsis>
-        <usage>  Dial(Technology/resource[&amp;Technology2/resource2...][|timeout][|options][|URL]):
-Requests one or more channels and places specified outgoing calls on them.
-As soon as a channel answers, the Dial app will answer the originating
-channel (if it needs to be answered) and will bridge a call with the channel
-which first answered. All other calls placed by the Dial app will be hung up
-If a timeout is not specified, the Dial application will wait indefinitely
-until either one of the called channels answers, the user hangs up, or all
-channels return busy or error. In general, the dialer will return 0 if it
-was unable to place the call, or the timeout expired. However, if all
-channels were busy, and there exists an extension with priority n+101 (where
-n is the priority of the dialer instance), then it will be the next
-executed extension (this allows you to setup different behavior on busy from
-no-answer).
-  This application returns -1 if the originating channel hangs up, or if the
-call is bridged and either of the parties in the bridge terminate the call.
-The option string may contain zero or more of the following characters:
-      &#39;t&#39; -- allow the called user transfer the calling user by hitting #.
-      &#39;T&#39; -- allow the calling user to transfer the call by hitting #.
-      &#39;f&#39; -- Forces callerid to be set as the extension of the line 
-             making/redirecting the outgoing call. For example, some PSTNs
-             don&#39;t allow callerids from other extensions then the ones
-             that are assigned to you.
-      &#39;r&#39; -- indicate ringing to the calling party, pass no audio until answered.
-      &#39;m&#39; -- provide hold music to the calling party until answered.
-      &#39;M(x) -- Executes the macro (x) upon connect of the call
-      &#39;h&#39; -- allow callee to hang up by hitting *.
-      &#39;H&#39; -- allow caller to hang up by hitting *.
-      &#39;C&#39; -- reset call detail record for this call.
-      &#39;P[(x)]&#39; -- privacy mode, using &#39;x&#39; as database if provided.
-      &#39;g&#39; -- goes on in context if the destination channel hangs up
-      &#39;e&#39; -- Force the caller to Explicitly accept the call
-      &#39;A(x)&#39; -- play an announcement to the called party, using x as file
-      &#39;S(x)&#39; -- hangup the call after x seconds AFTER called party picked up
-      &#39;D([digits])&#39;  -- Send DTMF digit string *after* called party has answered
-             but before the bridge. (w=500ms sec pause)
-      &#39;L(x[:y][:z])&#39; -- Limit the call to &#39;x&#39; ms warning when &#39;y&#39; ms are left
-             repeated every &#39;z&#39; ms) Only &#39;x&#39; is required, &#39;y&#39; and &#39;z&#39; are optional.
-             The following special variables are optional:
-             * LIMIT_PLAYAUDIO_CALLER    yes|no (default yes)
-                                         Play sounds to the caller.
-             * LIMIT_PLAYAUDIO_CALLEE    yes|no
-                                         Play sounds to the callee.
-             * LIMIT_TIMEOUT_FILE        File to play when time is up.
-             * LIMIT_CONNECT_FILE        File to play when call begins.
-             * LIMIT_WARNING_FILE        File to play as warning if &#39;y&#39; is defined.
-                        &#39;timeleft&#39; is a special sound macro to auto-say the time 
-                        left and is the default.
-
-  In addition to transferring the call, a call may be parked and then picked
-up by another user.
-  The optional URL will be sent to the called party if the channel supports it.
-  This application sets the following channel variables upon completion:
-      DIALEDTIME    Time from dial to answer
-      ANSWEREDTIME  Time for actual call
-      DIALSTATUS    The status of the call as a text string, one of
-             CHANUNAVAIL | CONGESTION | NOANSWER | BUSY | ANSWER | CANCEL
-
-</usage>
-    </application>
-    <application name="ParkedCall">
-        <synopsis>Answer a parked call</synopsis>
-        <usage>ParkedCall(exten):Used to connect to a parked call.  This Application is always
-registered internally and does not need to be explicitly added
-into the dialplan, although you should include the &#39;parkedcalls&#39;
-context.
-
-</usage>
-    </application>
-    <application name="ResponseTimeout">
-        <synopsis>Set maximum timeout awaiting response</synopsis>
-        <usage>  ResponseTimeout(seconds): Set the maximum amount of time permitted after
-falling through a series of priorities for a channel in which the user may
-begin typing an extension. If the user does not type an extension in this
-amount of time, control will pass to the &#39;t&#39; extension if it exists, and
-if not the call would be terminated. The default timeout is 10 seconds.
-Always returns 0.
-
-</usage>
-    </application>
-    <application name="Hangup">
-        <synopsis>Unconditional hangup</synopsis>
-        <usage>  Hangup(): Unconditionally hangs up a given channel by returning -1 always.
-
-</usage>
-    </application>
-    <application name="GotoIfTime">
-        <synopsis>Conditional goto on current time</synopsis>
-        <usage>  GotoIfTime(&lt;times&gt;|&lt;weekdays&gt;|&lt;mdays&gt;|&lt;months&gt;?[[context|]extension|]pri):
-If the current time matches the specified time, then branch to the specified
-extension. Each of the elements may be specified either as &#39;*&#39; (for always)
-or as a range. See the &#39;include&#39; syntax for details.
-</usage>
-    </application>
-    <application name="NoOp">
-        <synopsis>No operation</synopsis>
-        <usage>  NoOp(): No-operation; Does nothing.
-</usage>
-    </application>
-    <application name="SetCallerPres">
-        <synopsis>Set CallerID Presentation</synopsis>
-        <usage>  SetCallerPres(presentation): Set Caller*ID presentation on
-a call to a new value.  Sets ANI as well if a flag is used.
-Always returns 0.  Valid presentations are:
-
-      allowed_not_screened    : Presentation Allowed, Not Screened
-      allowed_passed_screen   : Presentation Allowed, Passed Screen
-      allowed_failed_screen   : Presentation Allowed, Failed Screen
-      allowed                 : Presentation Allowed, Network Number
-      prohib_not_screened     : Presentation Prohibited, Not Screened
-      prohib_passed_screen    : Presentation Prohibited, Passed Screen
-      prohib_failed_screen    : Presentation Prohibited, Failed Screen
-      prohib                  : Presentation Prohibited, Network Number
-      unavailable             : Number Unavailable
-
-
-</usage>
-    </application>
-    <application name="SetLanguage">
-        <synopsis>Sets user language</synopsis>
-        <usage>  SetLanguage(language):  Set  the  channel  language to &#39;language&#39;.  This
-information is used for the syntax in generation of numbers, and to choose
-a natural language file when available.
-  For example, if language is set to &#39;fr&#39; and the file &#39;demo-congrats&#39; is 
-requested  to  be  played,  if the file &#39;fr/demo-congrats&#39; exists, then
-it will play that file, and if not will play the normal &#39;demo-congrats&#39;.
-Always returns 0.
-
-</usage>
-    </application>
-    <application name="NoCDR">
-        <synopsis>Make sure asterisk doesn&#39;t save CDR for a certain call</synopsis>
-        <usage>NoCDR(): makes sure there won&#39;t be any CDR written for a certain call
-</usage>
-    </application>
-    <application name="StripMSD">
-        <synopsis>Strip leading digits</synopsis>
-        <usage>  StripMSD(count): Strips the leading &#39;count&#39; digits from the channel&#39;s
-associated extension. For example, the number 5551212 when stripped with a
-count of 3 would be changed to 1212. This app always returns 0, and the PBX
-will continue processing at the next priority for the *new* extension.
-  So, for example, if priority 3 of 5551212 is StripMSD 3, the next step
-executed will be priority 4 of 1212. If you switch into an extension which
-has no first step, the PBX will treat it as though the user dialed an
-invalid extension.
-
-</usage>
-    </application>
-    <application name="Progress">
-        <synopsis>Indicate progress</synopsis>
-        <usage>  Progress(): Request that the channel indicate in-band progress is 
-available to the user.
-Always returns 0.
-
-</usage>
-    </application>
-    <application name="ZapBarge">
-        <synopsis>Barge in (monitor) Zap channel</synopsis>
-        <usage>  ZapBarge([channel]): Barges in on a specified zap
-channel or prompts if one is not specified.  Returns
--1 when caller user hangs up and is independent of the
-state of the channel being monitored.
-</usage>
-    </application>
-    <application name="GotoIf">
-        <synopsis>Conditional goto</synopsis>
-        <usage>  GotoIf(Condition?label1:label2): Go to label 1 if condition is
-true, to label2 if condition is false. Either label1 or label2 may be
-omitted (in that case, we just don&#39;t take the particular branch) but not
-both. Look for the condition syntax in examples or documentation.
-</usage>
-    </application>
-</asterisk>
\ No newline at end of file
diff --git a/config/conf.xml b/config/conf.xml
deleted file mode 100644 (file)
index 3175115..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0"?>
-<!-- $Id$ -->
-<configuration>
- <configtab name="storage" desc="Storage">
-  <configsection name="storage">
-   <configheader>Context Storage</configheader>
-   <configswitch name="driver" desc="What backend should we use for storing the list of valid contexts/customers?">Sql
-    <case name="Sql" desc="SQL">
-     <configsection name="params">
-      <configsql switchname="driverconfig" />
-      <configstring name="table" desc="Table to hold the list of contexts/customers" required="true">contexts</configstring>
-     </configsection>
-    </case>
-   </configswitch>
-  </configsection>
- </configtab>
- <configtab name="extensions" desc="Extensions">
-  <configsection name="extensions">
-   <configheader>Extension Storage</configheader>
-   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">Ldap
-    <case name="Ldap" desc="LDAP">
-     <configsection name="params">
-      <configldap switchname="driverconfig" />
-     </configsection>
-    </case>
-    <case name="Sql" desc="SQL">
-     <configsection name="params">
-      <configsql switchname="driverconfig" />
-      <configstring name="table" desc="Table to hold the list of extensions" required="true"></configstring>
-     </configsection>
-    </case>
-   </configswitch>
-  </configsection>
- </configtab>
- <configtab name="devices" desc="VoIP Devices">
-  <configsection name="devices">
-   <configheader>Device Storage</configheader>
-   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">Ldap
-    <case name="Ldap" desc="LDAP">
-     <configsection name="params">
-      <configldap switchname="driverconfig" />
-     </configsection>
-    </case>
-    <case name="Sql" desc="SQL">
-     <configsection name="params">
-      <configsql switchname="driverconfig" />
-      <configstring name="table" desc="Table to hold the device configuration data" required="true">sip_peers</configstring>
-     </configsection>
-    </case>
-   </configswitch>
-  </configsection>
- </configtab>
-</configuration>
-
diff --git a/devices.php b/devices.php
deleted file mode 100644 (file)
index cac6319..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * Copyright 2009 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- */
-@define('SHOUT_BASE', dirname(__FILE__));
-require_once SHOUT_BASE . '/lib/base.php';
-require_once SHOUT_BASE . '/lib/Forms/DeviceForm.php';
-
-$action = Horde_Util::getFormData('action');
-$devices = $shout_devices->getDevices($context);
-$devid = Horde_Util::getFormData('devid');
-$vars = Horde_Variables::getDefaultVariables();
-
-//$tabs = Shout::getTabs($context, $vars);
-
-$RENDERER = new Horde_Form_Renderer();
-
-$section = 'devices';
-$title = _("Devices: ");
-
-switch ($action) {
-    case 'save':
-        $Form = new DeviceDetailsForm($vars);
-
-        // Show the list if the save was successful, otherwise back to edit.
-        if ($Form->isSubmitted() && $Form->isValid()) {
-            try {
-                $shout_devices->saveDevice($Form->getVars());
-                $notification->push(_("Device settings saved."));
-            } catch (Exception $e) {
-                $notification->push($e);
-            }
-            $action = 'list';
-            break;
-        } else {
-            $action = 'edit';
-        }
-    case 'add':
-    case 'edit':
-        if ($action == 'add') {
-            $title .= _("New Device");
-            // Treat adds just like an empty edit
-            $action = 'edit';
-        } else {
-            $title .= sprintf(_("Edit Device %s"), $extension);
-
-        }
-
-        $FormName = 'DeviceDetailsForm';
-        $vars = new Horde_Variables($devices[$devid]);
-        $Form = new DeviceDetailsForm($vars);
-
-        $Form->open($RENDERER, $vars, Horde::applicationUrl('devices.php'), 'post');
-
-        break;
-
-
-    case 'delete':
-        $notification->push("Not supported.");
-        break;
-
-    case 'list':
-    default:
-        $action = 'list';
-        $title .= _("List Users");
-}
-
-require SHOUT_TEMPLATES . '/common-header.inc';
-require SHOUT_TEMPLATES . '/menu.inc';
-
-$notification->notify();
-
-//echo $tabs->render($section);
-
-require SHOUT_TEMPLATES . '/devices/' . $action . '.inc';
-
-require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/dialplan.php b/dialplan.php
deleted file mode 100644 (file)
index 883d2c1..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @package shout
- */
-
-if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
-    header('Location: /');
-    exit();
-}
-
-require_once SHOUT_BASE . '/lib/Dialplan.php';
-$dialplan = &$shout->getDialplan($context);
-
-// Set up the tree.
-$dpgui = Shout_Dialplan::singleton('x', $dialplan);
-
-//$action = Horde_Util::getFormData("action");
-// $action = 'manager';
-
-$title = _("Dialplan Manager");
-
-require SHOUT_TEMPLATES . '/common-header.inc';
-require SHOUT_TEMPLATES . '/menu.inc';
-
-$notification->notify();
-
-echo $tabs->render($section);
-
-// require SHOUT_BASE . "/dialplan/$action.php";
-
-require SHOUT_TEMPLATES . '/dialplan/manager.inc';
-
-// Horde::addScriptFile('httpclient.js', 'horde', true);
-// Horde::addScriptFile('hideable.js', 'horde', true);
-// require HORDE_TEMPLATES . '/common-header.inc';
-// require HORDE_TEMPLATES . '/portal/sidebar.inc';
-
-
-// require SHOUT_TEMPLATES . "/dialplan/dialplanlist.inc";
-
-
-
-
-
-
-
-
-
-
-require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/dialplan/edit.php b/dialplan/edit.php
deleted file mode 100644 (file)
index 18ef2b4..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- */
-@define('SHOUT_BASE', dirname(__FILE__) . '/..');
-require_once SHOUT_BASE . '/lib/Dialplan.php';
-require_once 'Horde/Variables.php';
-
-$RENDERER = &new Horde_Form_Renderer();
-
-$empty = '';
-
-$vars = &Variables::getDefaultVariables($empty);
-$formname = $vars->get('formname');
-$context = $vars->get('context');
-$extension = $vars->get('extension');
-$dialplan = &$shout->getDialplan($context);
-
-$ExtensionDetailsForm = &Horde_Form::singleton('ExtensionDetailsForm', $vars);
-$ExtensionDetailsFormValid = $ExtensionDetailsForm->validate($vars, true);
-
-$ExtensionDetailsForm->open($RENDERER, $vars, 'dialplan.php', 'post');
-$ExtensionDetailsForm->preserveVarByPost($vars, "section");
-$ExtensionDetailsForm->preserve($vars);
-require SHOUT_TEMPLATES . '/table-limiter-begin.inc';
-$RENDERER->beginActive($ExtensionDetailsForm->getTitle());
-$RENDERER->renderFormActive($ExtensionDetailsForm, $vars);
-# FIXME Maybe this should be a subclass inheriting from the From/Renderer object
-# instead of a simple include?
-$i = 0;
-require SHOUT_TEMPLATES . '/dialplan/priority-form-begin.inc';
-foreach ($dialplan['extensions'][$extension] as $priority => $application) {
-    require SHOUT_TEMPLATES . '/dialplan/priority-form-line.inc';
-    $i++;
-}
-require SHOUT_TEMPLATES . '/dialplan/priority-form-end.inc';
-$RENDERER->submit('Add Priority');
-$RENDERER->submit('Add 5 Priorities');
-$RENDERER->submit('Save');
-$RENDERER->end();
-$ExtensionDetailsForm->close($RENDERER);
-require SHOUT_TEMPLATES . '/table-limiter-end.inc';
\ No newline at end of file
diff --git a/extensions.php b/extensions.php
deleted file mode 100644 (file)
index 295d165..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- */
-@define('SHOUT_BASE', dirname(__FILE__));
-require_once SHOUT_BASE . '/lib/base.php';
-require_once SHOUT_BASE . '/lib/Forms/ExtensionForm.php';
-//require_once SHOUT_BASE . '/lib/Shout.php';
-
-$action = Horde_Util::getFormData('action');
-$extension = Horde_Util::getFormData('extension');
-$extensions = $shout_extensions->getExtensions($context);
-
-$vars = Horde_Variables::getDefaultVariables();
-
-//$tabs = Shout::getTabs($context, $vars);
-
-$RENDERER = new Horde_Form_Renderer();
-
-$section = 'extensions';
-$title = _("Extensions: ");
-
-switch ($action) {
-case 'save':
-    $title .= sprintf(_("Save Extension %s"), $extension);
-    $FormName = $vars->get('formname');
-
-    $Form = &Horde_Form::singleton($FormName, $vars);
-
-    $FormValid = $Form->validate($vars, true);
-
-    if ($Form->isSubmitted() && $FormValid) {
-        // Form is Valid and Submitted
-        try {
-            $Form->execute();
-        } catch (Exception $e) {
-            $notification->push($e);
-        }
-        $notification->push(_("User information updated."),
-                                  'horde.success');
-        break;
-     } else {
-         $action = 'edit';
-         // Fall-through to the "edit" action
-     }
-
-case 'add':
-case 'edit':
-    if ($action == 'add') {
-        $title .= _("New Extension");
-        // Treat adds just like an empty edit
-        $action = 'edit';
-    } else {
-        $title .= sprintf(_("Edit Extension %s"), $extension);
-
-    }
-
-    $FormName = 'ExtensionDetailsForm';
-    $vars = new Horde_Variables($extensions[$extension]);
-    $Form = &Horde_Form::singleton($FormName, $vars);
-
-    $Form->open($RENDERER, $vars, Horde::applicationUrl('extensions.php'), 'post');
-
-    break;
-
-case 'delete':
-    $title .= sprintf(_("Delete Extension %s"), $extension);
-    $extension = Horde_Util::getFormData('extension');
-
-    $res = $shout->deleteUser($context, $extension);
-
-    if (!$res) {
-        echo "Failed!";
-        print_r($res);
-    }
-    $notification->push("User Deleted.");
-    break;
-
-case 'list':
-default:
-    $action = 'list';
-    $title .= _("List Users");
-}
-
-require SHOUT_TEMPLATES . '/common-header.inc';
-require SHOUT_TEMPLATES . '/menu.inc';
-
-$notification->notify();
-
-//echo $tabs->render($section);
-
-require SHOUT_TEMPLATES . '/extensions/' . $action . '.inc';
-
-require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/index.php b/index.php
deleted file mode 100644 (file)
index 4097661..0000000
--- a/index.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @package shout
- */
-
-@define('SHOUT_BASE', dirname(__FILE__));
-$shout_configured = (is_readable(SHOUT_BASE . '/config/conf.php'));
-
-if (!$shout_configured) {
-require SHOUT_BASE . '/../lib/Test.php';
-    Horde_Test::configFilesMissing('Shout', SHOUT_BASE,
-        array('conf.php'));
-}
-
-require_once SHOUT_BASE . '/lib/base.php';
-header('Location: ' . Horde::applicationUrl('extensions.php'));
diff --git a/lib/Dialplan.php b/lib/Dialplan.php
deleted file mode 100644 (file)
index 0549b32..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- *
- * @package Shout
- */
-// {{{
-/**
- * The Shout_Dialplan:: class provides an interactive view of an Asterisk dialplan.
- * It allows for expanding/collapsing of extensions and priorities and maintains their state.
- * It can work together with the Horde_Tree javascript class to achieve this in
- * DHTML on supported browsers.
- *
- * Copyright 2005 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * $Id$
- *
- * @author  Ben Klang <ben@alkaloid.net>
- * @package Shout_Dialplan
- * @since   Shout 0.1
- */
-class Shout_Dialplan
-{
-    /**
-     * The name of this instance.
-     *
-     * @var string
-     */
-    var $_instance = null;
-
-    /**
-     * The array of dialplan information to render the form
-     *
-     * @var array
-     */
-    var $_dialplan = array();
-
-    /**
-     * Object containing the instantiation of the Horde_Tree class
-     *
-     * @var object
-     */
-     var $_tree = null;
-
-    /**
-     * Create or return a unique instance of the Shout_Dialplan object
-     *
-     * @param string $instance Unique identifier for this instance
-     * @param array $dialplan Dialplan array as returned by the driver
-     * @return object Instantiation of the Shout_Dialplan object
-     */
-     function &singleton($instance, $dialplan)
-     {
-        static $instances = array();
-
-        if (isset($instances[$instance])) {
-            return $instances[$instance];
-        }
-        $instances[$instance] = new Shout_Dialplan($instance, $dialplan);
-        return $instances[$instance];
-    }
-
-    /**
-     * Instantiator for the Shout_Dialplan
-     *
-     * @param string $instance Unique identifier for this instance
-     * @param array $dialplan Dialplan array as returned by the driver
-     * @return Shout_Dialplan Instantiation of the Shout_Dialplan object
-     */
-    function Shout_Dialplan($instance, $dialplan)
-    {
-        require_once 'Horde/Tree.php';
-        require_once 'Horde/Block.php';
-        require_once 'Horde/Block/Collection.php';
-
-        $this->_instance = $instance;
-        $this->_dialplan = $dialplan;
-        $this->_tree = Horde_Tree::singleton('shout_dialplan_nav_'.$instance, 'javascript');
-
-        foreach ($this->_dialplan as $linetype => $linedata) {
-            switch($linetype) {
-                case 'extensions':
-                    $url = '#top';
-                    $this->_tree->addNode('extensions', null, 'Extensions', null, array('url' => $url));
-                    foreach ($linedata as $extension => $priorities) {
-                        $nodetext = Shout::exten2name($extension);
-                        $url = Horde::applicationUrl('index.php?section=dialplan' .
-                            '&extension=' . $extension . '&context=' . $this->_dialplan['name']);
-                        $url = "#$extension";
-                        $this->_tree->addNode("extension_".$extension, 'extensions', $nodetext,
-                            null, false,
-                            array(
-                                'url' => $url,
-                                'onclick' =>
-                                    'shout_dialplan_object_'.$this->_instance.
-                                        '.highlightExten(\''.$extension.'\')',
-                            )
-                        );
-        //                 foreach ($priorities as $priority => $application) {
-        //                     $this->_tree->addNode("$extension-$priority", $extension, "$priority: $application", null);
-        //                 }
-                    }
-                    break;
-
-                case 'includes':
-                    $this->_tree->addNode('includes', null, 'Includes', null);
-                    foreach ($linedata as $include) {
-                        $url = Horde::applicationUrl('index.php?section=dialplan&context='.$include);
-                        $this->_tree->addNode("include_$include", 'includes', $include, null,
-                            true, array('url' => $url));
-                    }
-                    break;
-
-                # TODO Ignoring ignorepat lines for now
-
-                case 'barelines':
-                    $this->_tree->addNode('barelines', null, 'Extra Settings', null);
-                    $i = 0;
-                    foreach ($linedata as $bareline) {
-                        $this->_tree->addNode("bareline_".$i, 'barelines', $bareline, null);
-                        $i++;
-                    }
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Render dialplan side navigation tree
-     */
-    function renderNavTree()
-    {
-        print '<div id=\'contextTree\'>'."\n";
-        $this->_tree->renderTree(true);
-        print '    <br />'."\n";
-        print '    <a href="#top" class="small">Back to Top</a>'."\n";
-        print '</div>'."\n";
-        return true;
-    }
-
-    function generateAppList()
-    {
-        $applist = Shout::getApplist();
-        print '<script language="JavaScript" type="text/javascript">'."\n";
-        print '<!--'."\n";
-        print 'var shout_dialplan_applist_'.$this->_instance.' = new Array();'."\n";
-
-        $i = 0;
-        foreach ($applist as $app => $appdata) {
-            print 'shout_dialplan_applist_'.$this->_instance.'['.$i.'] = \''.$app.'\''."\n";
-            $i++;
-        }
-        print '//-->'."\n";
-        print '</script>'."\n";
-        return true;
-    }
-
-    function renderExtensions()
-    {
-        if(!isset($this->_dialplan['extensions'])) {
-            print '<div id="extensionDetail">'."\n";
-            print '    <div class="extensionBox">No Configured Extensions</div>'."\n";
-            print '</div>'."\n";
-        } else {
-            print '<script language="JavaScript" type="text/javascript"';
-            print ' src="/services/javascript.php?file=dialplan.js&amp;app=shout"></script>'."\n";
-            print '<script language="JavaScript" type="text/javascript">'."\n";
-            print '<!--'."\n";
-            print 'var shout_dialplan_entry_'.$this->_instance.' = new Array();'."\n";
-            foreach($this->_dialplan['extensions'] as $extension => $priorities) {
-                print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'] = new Array();'."\n";
-                print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'][\'name\'] =';
-                print '\''.Shout::exten2name($extension).'\';'."\n";
-                print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'][\'priorities\']';
-                print ' = new Array();'."\n";
-                foreach($priorities as $priority => $data) {
-                    print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\']';
-                    print '[\'priorities\']['.$priority.'] = new Array();'."\n";
-                    print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\']';
-                    print '[\'priorities\']['.$priority.'][\'application\'] = ';
-                    print '\''.$data['application'].'\';'."\n";
-                    print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'][\'priorities\']';
-                    print '['.$priority.'][\'args\'] = \''.$data['args'].'\';'."\n";
-                }
-            }
-            print 'var shout_dialplan_object_'.$this->_instance.' = new Dialplan(\''.$this->_instance.'\');'."\n";
-            print '//-->'."\n";
-            print '</script>'."\n";
-
-            print '<form id="shout_dialplan_form_'.$this->_instance.'" action="#">'."\n";
-            print '<div id="extensionDetail">'."\n";
-            $e = 0;
-            foreach($this->_dialplan['extensions'] as $extension => $priorities) {
-                print '<div class="extension" ';
-                    print 'id="extension_'.$extension.'">';
-                    print '<div class="extensionBox" ';
-                        print 'id="eBox-'.$extension.'" ';
-                        print 'onclick="javascript:shout_dialplan_object_'.$this->_instance.'.highlightExten';
-                            print '(\''.$extension.'\');">'."\n";
-                        print '<a name="'.$extension.'">'."\n";
-                            print Shout::exten2name($extension);
-                        print '</a>'."\n";
-                    print '</div>'."\n";
-                    print '<div id="pList-'.$extension.'">'."\n";
-                    print '</div>'."\n";
-                $e++;
-                print '</div>'."\n";
-                print '<br />'."\n";
-                print '<script language="JavaScript" type="text/javascript">'."\n";
-                print '<!--'."\n";
-                print 'shout_dialplan_object_'.$this->_instance.'.drawPrioTable(\''.$extension.'\');'."\n";
-                print '//-->'."\n";
-                print '</script>'."\n";
-            }
-            print '</div>'."\n";
-            print '</form>'."\n";
-        }
-    }
-}
\ No newline at end of file
diff --git a/lib/Driver.php b/lib/Driver.php
deleted file mode 100644 (file)
index 421afd3..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-<?php
-/**
- * Shout_Driver:: defines an API for implementing storage backends for Shout.
- *
- * $Id$
- *
- * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author  Ben Klang <ben@alkaloid.net>
- * @version $Revision: 76 $
- * @since   Shout 0.1
- * @package Shout
- */
-
-class Shout_Driver {
-
-    /**
-     * Hash containing connection parameters.
-     *
-     * @var array $_params
-     */
-    var $_params = array();
-
-    function Shout_Driver($params = array())
-    {
-        $this->_params = $params;
-    }
-
-    /**
-    * Get a list of contexts from the instantiated driver and filter
-    * the returned contexts for those which the current user can see/edit
-    *
-    * @param optional string $filter Filter for types of contexts to return.
-    *                                One of "system" "customer" or "all"
-    *
-    * @param optional string $filterperms Filter contexts for given permissions
-    *
-    * @return array Contexts valid for this user
-    *
-    * @access public
-    */
-    function getContexts($filters = "all", $filterperms = null)
-    {
-        throw new Shout_Exception("This function is not implemented.");
-    }
-
-    /**
-     * For the given context and type, make sure the context has the
-     * appropriate properties, that it is effectively of that "type"
-     *
-     * @param string $context the context to check type for
-     *
-     * @param string $type the type to verify the context is of
-     *
-     * @return boolean true of the context is of type, false if not
-     *
-     * @access public
-     */
-    function checkContextType($context, $type)
-    {
-        throw new Shout_Exception("This function is not implemented.");
-    }
-
-    /**
-     * Get a list of users valid for the current context.  Return an array
-     * indexed by the extension.
-     *
-     * @param string $context Context for which users should be returned
-     *
-     * @return array User information indexed by voice mailbox number
-     */
-    function getUsers($context)
-    {
-        throw new Shout_Exception("This function is not implemented.");
-    }
-
-    /**
-     * Returns the name of the user's default context
-     *
-     * @return string User's default context
-     */
-    function getHomeContext()
-    {
-        throw new Shout_Exception("This function is not implemented.");
-    }
-
-    /**
-     * Get a context's properties
-     *
-     * @param string $context Context to get properties for
-     *
-     * @return integer Bitfield of properties valid for this context
-     */
-    function getContextProperties($context)
-    {
-        throw new Shout_Exception("This function is not implemented.");
-    }
-
-    /**
-     * Get a context's extensions and return as a multi-dimensional associative
-     * array
-     *
-     * @param string $context Context to return extensions for
-     *
-     * @return array Multi-dimensional associative array of extensions data
-     *
-     */
-    function getDialplan($context)
-    {
-        throw new Shout_Exception("This function is not implemented.");
-    }
-
-    /**
-     * Attempts to return a concrete Shout_Driver instance based on
-     * $driver.
-     *
-     * @param string $driver  The type of the concrete Shout_Driver subclass
-     *                        to return.  The class name is based on the storage
-     *                        driver ($driver).  The code is dynamically
-     *                        included.
-     *
-     * @param array  $params  (optional) A hash containing any additional
-     *                        configuration or connection parameters a
-     *                        subclass might need.
-     *
-     * @return mixed  The newly created concrete Shout_Driver instance, or
-     *                false on an error.
-     */
-    function &factory($class, $driver = null, $params = null)
-    {
-        if (is_null($driver)) {
-            $driver = $GLOBALS['conf'][$class]['driver'];
-        }
-
-        $driver = basename($driver);
-
-        if (is_null($params)) {
-            if ($GLOBALS['conf'][$class]['params']['driverconfig'] == 'horde') {
-                $params = array_merge(Horde::getDriverConfig('storage', $driver),
-                                      $GLOBALS['conf'][$class]['params']);
-            } else {
-                $params = $GLOBALS['conf'][$class]['params'];
-            }
-        }
-
-        $params['class'] = $class;
-
-        require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
-        $class = 'Shout_Driver_' . $driver;
-        if (class_exists($class)) {
-            return new $class($params);
-        } else {
-            return false;
-        }
-    }
-
-}
diff --git a/lib/Driver/Ldap.php b/lib/Driver/Ldap.php
deleted file mode 100644 (file)
index ee0358f..0000000
+++ /dev/null
@@ -1,776 +0,0 @@
-<?php
-
-class Shout_Driver_Ldap extends Shout_Driver
-{
-    var $_ldapKey;  // Index used for storing objects
-    var $_appKey;   // Index used for moving info to/from the app
-
-    /**
-     * Handle for the current database connection.
-     * @var object LDAP $_LDAP
-     */
-    private $_LDAP;
-
-    /**
-     * Boolean indicating whether or not we're connected to the LDAP
-     * server.
-     * @var boolean $_connected
-     */
-    private $_connected = false;
-
-
-    /**
-    * Constructs a new Shout LDAP driver object.
-    *
-    * @param array  $params    A hash containing connection parameters.
-    */
-    function __construct($params = array())
-    {
-        parent::__construct($params);
-        $this->_connect();
-
-        /* These next lines will translate between indexes used in the
-         * application and LDAP.  The rationale is that translation here will
-         * help make Congregation more driver-independant.  The keys used to
-         * contruct user arrays should be more appropriate to human-legibility
-         * (name instead of 'cn' and email instead of 'mail').  This translation
-         * is only needed because LDAP indexes users based on an arbitrary
-         * attribute and the application indexes by extension/context.  In my
-         * environment users are indexed by their 'mail' attribute and others
-         * may index based on 'cn' or 'uid'.  Any time a new $prefs['uid'] needs
-         * to be supported, this function should be checked and possibly
-         * extended to handle that translation.
-         */
-        switch($this->_params['uid']) {
-        case 'cn':
-            $this->_ldapKey = 'cn';
-            $this->_appKey = 'name';
-            break;
-        case 'mail':
-            $this->_ldapKey = 'mail';
-            $this->_appKey = 'email';
-            break;
-        case 'uid':
-            # FIXME Probably a better app key to map here
-            # There is no value that maps uid to LDAP so we can choose to use
-            # either extension or name, or anything really.  I want to
-            # support it since it's a very common DN attribute.
-            # Since it's entirely administrator's preference, I'll
-            # set it to name for now
-            $this->_ldapKey = 'uid';
-            $this->_appKey = 'name';
-            break;
-        case 'voiceMailbox':
-            $this->_ldapKey = 'voiceMailbox';
-            $this->_appKey = 'extension';
-            break;
-        }
-    }
-
-    /**
-     * Get a list of users valid for the contexts
-     *
-     * @param string $context  Context in which to search
-     *
-     * @return array User information indexed by voice mailbox number
-     */
-    public function getExtensions($context)
-    {
-
-        static $entries = array();
-        if (isset($entries[$context])) {
-            return $entries[$context];
-        }
-
-        $this->_params['basedn'];
-
-        $filter  = '(&';
-        $filter .= '(objectClass=AsteriskVoiceMail)';
-        $filter .= '(AstContext='.$context.')';
-        $filter .= ')';
-
-        $attributes = array(
-            'cn',
-            'mail',
-            'AstVoicemailMailbox',
-            'AstVoicemailPassword',
-            'AstVoicemailOptions',
-            'AstVoicemailPager',
-            'telephoneNumber',
-            'AstExtension'
-        );
-
-        $search = ldap_search($this->_LDAP, $this->_params['basedn'], $filter, $attributes);
-        if ($search === false) {
-            throw new Shout_Exception("Unable to search directory: " .
-                ldap_error($this->_LDAP), ldap_errno($this->_LDAP));
-        }
-
-        $res = ldap_get_entries($this->_LDAP, $search);
-        if ($res === false) {
-            throw new Shout_Exception("Unable to fetch results from directory: " .
-                ldap_error($this->_LDAP), ldap_errno($this->_LDAP));
-        }
-
-        // ATTRIBUTES RETURNED FROM ldap_get_entries ARE ALL LOWER CASE!!
-        // It's a PHP thing.
-        $entries[$context] = array();
-        $i = 0;
-        while ($i < $res['count']) {
-            list($extension) = explode('@', $res[$i]['astvoicemailmailbox'][0]);
-            $entries[$context][$extension] = array('extension' => $extension);
-
-            $j = 0;
-            $entries[$context][$extension]['mailboxopts'] = array();
-            if (empty($res[$i]['astvoicemailoptions']['count'])) {
-                $res[$i]['astvoicemailoptions']['count'] = -1;
-            }
-            while ($j < $res[$i]['astvoicemailoptions']['count']) {
-                $entries[$context][$extension]['mailboxopts'][] =
-                    $res[$i]['astvoicemailoptions'][$j];
-                $j++;
-            }
-
-            $entries[$context][$extension]['mailboxpin'] =
-                $res[$i]['astvoicemailpassword'][0];
-
-            $entries[$context][$extension]['name'] =
-                $res[$i]['cn'][0];
-
-            $entries[$context][$extension]['email'] =
-                $res[$i]['mail'][0];
-
-            $entries[$context][$extension]['pageremail'] =
-                $res[$i]['astvoicemailpager'][0];
-
-            $j = 0;
-            $entries[$context][$extension]['numbers'] = array();
-            if (empty($res[$i]['telephonenumber']['count'])) {
-                $res[$i]['telephonenumber']['count'] = -1;
-            }
-            while ($j < $res[$i]['telephonenumber']['count']) {
-                $entries[$context][$extension]['numbers'][] =
-                    $res[$i]['telephonenumber'][$j];
-                $j++;
-            }
-
-            $j = 0;
-            $entries[$context][$extension]['devices'] = array();
-            if (empty($res[$i]['astextension']['count'])) {
-                $res[$i]['astextension']['count'] = -1;
-            }
-            while ($j < $res[$i]['astextension']['count']) {
-                $entries[$context][$extension]['devices'][] =
-                    $res[$i]['astextension'][$j];
-                $j++;
-            }
-
-
-            $i++;
-
-        }
-
-        ksort($entries[$context]);
-
-        return($entries[$context]);
-    }
-
-    /**
-     * Get a list of destinations valid for this extension.
-     * A destination is either a telephone number, a VoIP device or an
-     * Instant Messaging address (a special case of VoIP).
-     *
-     * @param string $context    Context for the extension
-     * @param string $extension  Extension for which to return destinations
-     */
-    function getDestinations($context, $extension)
-    {
-        // FIXME: LDAP filter injection
-        $filter = '(&(AstContext=%s)(AstVoicemailMailbox=%s))';
-        $filter = sprintf($filter, $context, $extension);
-
-        $attrs = array('telephoneNumber', 'AstExtensions');
-
-        $res = ldap_search($this->_LDAP, $this->_params['basedn'],
-                           $filter, $attrs);
-
-        if ($res === false) {
-            $msg = sprintf('Error while searching LDAP.  Code %s; Message "%s"',
-                           ldap_errno($this->_LDAP), ldap_error($this->_LDAP));
-            Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
-            throw new Shout_Exception(_("Internal error searching the directory."));
-        }
-
-        $res = ldap_get_entries($this->_LDAP, $res);
-
-        if ($res === false) {
-            $msg = sprintf('Error while searching LDAP.  Code %s; Message "%s"',
-                           ldap_errno($this->_LDAP), ldap_error($this->_LDAP));
-            Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
-            throw new Shout_Exception(_("Internal error searching the directory."));
-        }
-
-        if ($res['count'] != 1) {
-            $msg = sprintf('Error while searching LDAP.  Code %s; Message "%s"',
-                           ldap_errno($this->_LDAP), ldap_error($this->_LDAP));
-            Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
-            throw new Shout_Exception(_("Wrong number of entries found for this search."));
-        }
-
-        return array('numbers' => $res['telephonenumbers'],
-                     'devices' => $res['astextensions']);
-    }
-
-    /**
-     * Get a context's dialplan and return as a multi-dimensional associative
-     * array
-     *
-     * @param string $context Context to return extensions for
-     *
-     * @param boolean $preprocess Parse includes and barelines and add their
-     *                            information into the extensions array
-     *
-     * @return array Multi-dimensional associative array of extensions data
-     *
-     */
-    public function getDialplan($context, $preprocess = false)
-    {
-        # FIXME Implement preprocess functionality.  Don't forget to cache!
-        static $dialplans = array();
-        if (isset($dialplans[$context])) {
-            return $dialplans[$context];
-        }
-
-        $res = @ldap_search($this->_LDAP,
-            SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
-            "(&(objectClass=".SHOUT_CONTEXT_EXTENSIONS_OBJECTCLASS.")(context=$context))",
-            array(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE, SHOUT_DIALPLAN_INCLUDE_ATTRIBUTE,
-                SHOUT_DIALPLAN_IGNOREPAT_ATTRIBUTE, 'description',
-                SHOUT_DIALPLAN_BARELINE_ATTRIBUTE));
-        if (!$res) {
-            return PEAR::raiseError("Unable to locate any extensions " .
-            "underneath ".SHOUT_ASTERISK_BRANCH.",".$this->_params['basedn'] .
-            " matching those search filters");
-        }
-
-        $res = ldap_get_entries($this->_LDAP, $res);
-        $dialplans[$context] = array();
-        $dialplans[$context]['name'] = $context;
-        $i = 0;
-        while ($i < $res['count']) {
-            # Handle extension lines
-            if (isset($res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)])) {
-                $j = 0;
-                while ($j < $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)]['count']) {
-                    @$line = $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)][$j];
-
-                    # Basic sanity check for length.  FIXME
-                    if (strlen($line) < 5) {
-                        break;
-                    }
-                    # Can't use strtok here because there may be commass in the
-                    # arg string
-
-                    # Get the extension
-                    $token1 = strpos($line, ',');
-                    $token2 = strpos($line, ',', $token1 + 1);
-                    $token3 = strpos($line, '(', $token2 + 1);
-
-                    $extension = substr($line, 0, $token1);
-                    if (!isset($dialplans[$context]['extensions'][$extension])) {
-                        $dialplan[$context]['extensions'][$extension] = array();
-                    }
-                    $token1++;
-                    # Get the priority
-                    $priority = substr($line, $token1, $token2 - $token1);
-                    $dialplans[$context]['extensions'][$extension][$priority] =
-                        array();
-                    $token2++;
-
-                    # Get Application and args
-                    $application = substr($line, $token2, $token3 - $token2);
-
-                    if ($token3) {
-                        $application = substr($line, $token2, $token3 - $token2);
-                        $args = substr($line, $token3);
-                        $args = preg_replace('/^\(/', '', $args);
-                        $args = preg_replace('/\)$/', '', $args);
-                    } else {
-                        # This application must not have any args
-                        $application = substr($line, $token2);
-                        $args = '';
-                    }
-
-                    # Merge all that data into the returning array
-                    $dialplans[$context]['extensions'][$extension][$priority]['application'] =
-                        $application;
-                    $dialplans[$context]['extensions'][$extension][$priority]['args'] =
-                        $args;
-                    $j++;
-                }
-
-                # Sort the extensions data
-                foreach ($dialplans[$context]['extensions'] as
-                    $extension => $data) {
-                    ksort($dialplans[$context]['extensions'][$extension]);
-                }
-                uksort($dialplans[$context]['extensions'],
-                    array(new Shout, "extensort"));
-            }
-            # Handle include lines
-            if (isset($res[$i]['asteriskincludeline'])) {
-                $j = 0;
-                while ($j < $res[$i]['asteriskincludeline']['count']) {
-                    @$line = $res[$i]['asteriskincludeline'][$j];
-                    $dialplans[$context]['includes'][$j] = $line;
-                    $j++;
-                }
-            }
-
-            # Handle ignorepat
-            if (isset($res[$i]['asteriskignorepat'])) {
-                $j = 0;
-                while ($j < $res[$i]['asteriskignorepat']['count']) {
-                    @$line = $res[$i]['asteriskignorepat'][$j];
-                    $dialplans[$context]['ignorepats'][$j] = $line;
-                    $j++;
-                }
-            }
-            # Handle ignorepat
-            if (isset($res[$i]['asteriskextensionbareline'])) {
-                $j = 0;
-                while ($j < $res[$i]['asteriskextensionbareline']['count']) {
-                    @$line = $res[$i]['asteriskextensionbareline'][$j];
-                    $dialplans[$context]['barelines'][$j] = $line;
-                    $j++;
-                }
-            }
-
-            # Increment object
-            $i++;
-        }
-        return $dialplans[$context];
-    }
-
-    /**
-     * Get the limits for the current user, the user's context, and global
-     * Return the most specific values in every case.  Return default values
-     * where no data is found.  If $extension is specified, $context must
-     * also be specified.
-     *
-     * @param optional string $context Context to search
-     *
-     * @param optional string $extension Extension/user to search
-     *
-     * @return array Array with elements indicating various limits
-     */
-     # FIXME Figure out how this fits into Shout/Congregation better
-    public function getLimits($context = null, $extension = null)
-    {
-
-        $limits = array('telephonenumbersmax',
-                        'voicemailboxesmax',
-                        'asteriskusersmax');
-
-        if(!is_null($extension) && is_null($context)) {
-            return PEAR::raiseError("Extension specified but no context " .
-                "given.");
-        }
-
-        if (!is_null($context) && isset($limits[$context])) {
-            if (!is_null($extension) &&
-                isset($limits[$context][$extension])) {
-                return $limits[$context][$extension];
-            }
-            return $limits[$context];
-        }
-
-        # Set some default limits (to unlimited)
-        static $cachedlimits = array();
-        # Initialize the limits with defaults
-        if (count($cachedlimits) < 1) {
-            foreach ($limits as $limit) {
-                $cachedlimits[$limit] = 99999;
-            }
-        }
-
-        # Collect the global limits
-        $res = @ldap_search($this->_LDAP,
-            SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
-            '(&(objectClass=asteriskLimits)(cn=globals))',
-            $limits);
-
-        if (!$res) {
-            return PEAR::raiseError('Unable to search the LDAP server for ' .
-                'global limits');
-        }
-
-        $res = ldap_get_entries($this->_LDAP, $res);
-        # There should only have been one object returned so we'll just take the
-        # first result returned
-        if ($res['count'] > 0) {
-            foreach ($limits as $limit) {
-                if (isset($res[0][$limit][0])) {
-                    $cachedlimits[$limit] = $res[0][$limit][0];
-                }
-            }
-        } else {
-            return PEAR::raiseError("No global object found.");
-        }
-
-        # Get limits for the context, if provided
-        if (isset($context)) {
-            $res = ldap_search($this->_LDAP,
-                SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
-                "(&(objectClass=asteriskLimits)(cn=$context))");
-
-            if (!$res) {
-                return PEAR::raiseError('Unable to search the LDAP server ' .
-                    "for $context specific limits");
-            }
-
-            $cachedlimits[$context][$extension] = array();
-            if ($res['count'] > 0) {
-                foreach ($limits as $limit) {
-                    if (isset($res[0][$limit][0])) {
-                        $cachedlimits[$context][$limit] = $res[0][$limit][0];
-                    } else {
-                        # If no value is provided use the global limit
-                        $cachedlimits[$context][$limit] = $cachedlimits[$limit];
-                    }
-                }
-            } else {
-
-                foreach ($limits as $limit) {
-                    $cachedlimits[$context][$limit] =
-                        $cachedlimits[$limit];
-                }
-            }
-
-            if (isset($extension)) {
-                $res = @ldap_search($this->_LDAP,
-                    SHOUT_USERS_BRANCH.','.$this->_params['basedn'],
-                    "(&(objectClass=asteriskLimits)(voiceMailbox=$extension)".
-                    "(context=$context))");
-
-                if (!$res) {
-                    return PEAR::raiseError('Unable to search the LDAP server '.
-                        "for Extension $extension, $context specific limits");
-                }
-
-                $cachedlimits[$context][$extension] = array();
-                if ($res['count'] > 0) {
-                    foreach ($limits as $limit) {
-                        if (isset($res[0][$limit][0])) {
-                            $cachedlimits[$context][$extension][$limit] =
-                                $res[0][$limit][0];
-                        } else {
-                            # If no value is provided use the context limit
-                            $cachedlimits[$context][$extension][$limit] =
-                                $cachedlimits[$context][$limit];
-                        }
-                    }
-                } else {
-                    foreach ($limits as $limit) {
-                        $cachedlimits[$context][$extension][$limit] =
-                            $cachedlimits[$context][$limit];
-                    }
-                }
-                return $cachedlimits[$context][$extension];
-            }
-            return $cachedlimits[$context];
-        }
-    }
-
-    /**
-     * Save an extension to the LDAP tree
-     *
-     * @param string $context Context to which the user should be added
-     *
-     * @param string $extension Extension to be saved
-     *
-     * @param array $details Phone numbers, PIN, options, etc to be saved
-     *
-     * @return TRUE on success, PEAR::Error object on error
-     */
-    public function saveExtension($context, $extension, $details)
-    {
-        $ldapKey = &$this->_ldapKey;
-        $appKey = &$this->_appKey;
-        # FIXME Access Control/Authorization
-        if (!Shout::checkRights("shout:contexts:$context:extensions", PERMS_EDIT, 1)) {
-            // FIXME: Allow users to edit themselves
-            //&& !($details[$appKey] == Auth::getAuth())) {
-            throw new Shout_Exception(_("Permission denied to save extensions in this context."));
-        }
-
-        $contexts = &$this->getContexts();
-//         $domain = $contexts[$context]['domain'];
-
-        # Check to ensure the extension is unique within this context
-        $filter = "(&(objectClass=AstVoicemailMailbox)(context=$context))";
-        $reqattrs = array('dn', $ldapKey);
-        $res = @ldap_search($this->_LDAP,
-            SHOUT_USERS_BRANCH . ',' . $this->_params['basedn'],
-            $filter, $reqattrs);
-        if (!$res) {
-            return PEAR::raiseError('Unable to check directory for duplicate extension: ' .
-                ldap_error($this->_LDAP));
-        }
-        if (($res['count'] > 1) ||
-            ($res['count'] != 0 &&
-            !in_array($res[0][$ldapKey], $details[$appKey]))) {
-            return PEAR::raiseError('Duplicate extension found.  Not saving changes.');
-        }
-
-        $entry = array(
-            'cn' => $details['name'],
-            'sn' => $details['name'],
-            'mail' => $details['email'],
-            'uid' => $details['email'],
-            'voiceMailbox' => $details['newextension'],
-            'voiceMailboxPin' => $details['mailboxpin'],
-            'context' => $context,
-            'asteriskUserDialOptions' => $details['dialopts'],
-        );
-
-        if (!empty ($details['telephonenumber'])) {
-            $entry['telephoneNumber'] = $details['telephonenumber'];
-        }
-
-        $validusers = &$this->getUsers($context);
-        if (!isset($validusers[$extension])) {
-            # Test to see if we're modifying an existing user that has
-            # no telephone system objectClasses and update that object/user
-            $rdn = $ldapKey.'='.$details[$appKey].',';
-            $branch = SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
-
-            # This test is something of a hack.  I want a cheap way to check
-            # for the existance of an object.  I don't want to do a full search
-            # so instead I compare that the dn equals the dn.  If the object
-            # exists then it'll return true.  If the object doesn't exist,
-            # it'll return error.  If it ever returns false something wierd
-            # is going on.
-            $res = @ldap_compare($this->_LDAP, $rdn.$branch,
-                    $ldapKey, $details[$appKey]);
-            if ($res === false) {
-                # We should never get here: a DN should ALWAYS match itself
-                return PEAR::raiseError("Internal Error: " . __FILE__ . " at " .
-                    __LINE__);
-            } elseif ($res === true) {
-                # The object/user exists but doesn't have the Asterisk
-                # objectClasses
-                $extension = $details['newextension'];
-
-                # $tmp is the minimal information required to establish
-                # an account in LDAP as required by the objectClasses.
-                # The entry will be fully populated below.
-                $tmp = array();
-                $tmp['objectClass'] = array(
-                    'asteriskUser',
-                    'asteriskVoiceMailbox'
-                );
-                $tmp['voiceMailbox'] = $extension;
-                $tmp['context'] = $context;
-                $res = @ldap_mod_replace($this->_LDAP, $rdn.$branch, $tmp);
-                if (!$res) {
-                    return PEAR::raiseError("Unable to modify the user: " .
-                        ldap_error($this->_LDAP));
-                }
-
-                # Populate the $validusers array to make the edit go smoothly
-                # below
-                $validusers[$extension] = array();
-                $validusers[$extension][$appKey] = $details[$appKey];
-
-                # The remainder of the work is done at the outside of the
-                # parent if() like a normal edit.
-
-            } elseif ($res === -1) {
-                # We must be adding a new user.
-                $entry['objectClass'] = array(
-                    'top',
-                    'person',
-                    'organizationalPerson',
-                    'inetOrgPerson',
-                    'hordePerson',
-                    'asteriskUser',
-                    'asteriskVoiceMailbox'
-                );
-
-                # Check to see if the maximum number of users for this context
-                # has been reached
-                $limits = $this->getLimits($context);
-                if (is_a($limits, "PEAR_Error")) {
-                    return $limits;
-                }
-                if (count($validusers) >= $limits['asteriskusersmax']) {
-                    return PEAR::raiseError('Maximum number of users reached.');
-                }
-
-                $res = @ldap_add($this->_LDAP, $rdn.$branch, $entry);
-                if (!$res) {
-                    return PEAR::raiseError('LDAP Add failed: ' .
-                        ldap_error($this->_LDAP));
-                }
-
-                return true;
-            } elseif (is_a($res, 'PEAR_Error')) {
-                # Some kind of internal error; not even sure if this is a
-                # possible outcome or not but I'll play it safe.
-                return $res;
-            }
-        }
-
-        # Anything after this point is an edit.
-
-        # Check to see if the object needs to be renamed (DN changed)
-        if ($validusers[$extension][$appKey] != $entry[$ldapKey]) {
-            $oldrdn = $ldapKey.'='.$validusers[$extension][$appKey];
-            $oldparent = SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
-            $newrdn = $ldapKey.'='.$entry[$ldapKey];
-            $res = @ldap_rename($this->_LDAP, "$oldrdn,$oldparent",
-                $newrdn, $oldparent, true);
-            if (!$res) {
-                return PEAR::raiseError('LDAP Rename failed: ' .
-                    ldap_error($this->_LDAP));
-            }
-        }
-
-        # Update the object/user
-        $dn = $ldapKey.'='.$entry[$ldapKey];
-        $dn .= ','.SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
-        $res = @ldap_modify($this->_LDAP, $dn, $entry);
-        if (!$res) {
-            return PEAR::raiseError('LDAP Modify failed: ' .
-                ldap_error($this->_LDAP));
-        }
-
-        # We must have been successful
-        return true;
-    }
-
-    /**
-     * Deletes a user from the LDAP tree
-     *
-     * @param string $context Context to delete the user from
-     * @param string $extension Extension of the user to be deleted
-     *
-     * @return boolean True on success, PEAR::Error object on error
-     */
-    public function deleteUser($context, $extension)
-    {
-        $ldapKey = &$this->_ldapKey;
-        $appKey = &$this->_appKey;
-
-        if (!Shout::checkRights("shout:contexts:$context:users",
-            PERMS_DELETE, 1)) {
-            return PEAR::raiseError("No permission to delete users in this " .
-                "context.");
-        }
-
-        $validusers = $this->getUsers($context);
-        if (!isset($validusers[$extension])) {
-            return PEAR::raiseError("That extension does not exist.");
-        }
-
-        $dn = "$ldapKey=".$validusers[$extension][$appKey];
-        $dn .= ',' . SHOUT_USERS_BRANCH . ',' . $this->_params['basedn'];
-
-        $res = @ldap_delete($this->_LDAP, $dn);
-        if (!$res) {
-            return PEAR::raiseError("Unable to delete $extension from " .
-                "$context: " . ldap_error($this->_LDAP));
-        }
-        return true;
-    }
-
-
-    /* Needed because uksort can't take a classed function as its callback arg */
-    protected function _sortexten($e1, $e2)
-    {
-        print "$e1 and $e2\n";
-        $ret =  Shout::extensort($e1, $e2);
-        print "returning $ret";
-        return $ret;
-    }
-
-    /**
-     * Attempts to open a connection to the LDAP server.
-     *
-     * @return boolean    True on success; exits (Horde::fatal()) on error.
-     *
-     * @access private
-     */
-    protected function _connect()
-    {
-        if ($this->_connected) {
-            return;
-        }
-
-        if (!Horde_Util::extensionExists('ldap')) {
-            throw new Horde_Exception('Required LDAP extension not found.');
-        }
-
-        Horde::assertDriverConfig($this->_params, $this->_params['class'],
-            array('hostspec', 'basedn', 'writedn'));
-
-        /* Open an unbound connection to the LDAP server */
-        $conn = ldap_connect($this->_params['hostspec'], $this->_params['port']);
-        if (!$conn) {
-             Horde::logMessage(
-                sprintf('Failed to open an LDAP connection to %s.',
-                        $this->_params['hostspec']),
-                __FILE__, __LINE__, PEAR_LOG_ERR);
-            throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
-        }
-
-        /* Set hte LDAP protocol version. */
-        if (isset($this->_params['version'])) {
-            $result = ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION,
-                                       $this->_params['version']);
-            if ($result === false) {
-                Horde::logMessage(
-                    sprintf('Set LDAP protocol version to %d failed: [%d] %s',
-                            $this->_params['version'],
-                            ldap_errno($conn),
-                            ldap_error($conn)),
-                    __FILE__, __LINE__, PEAR_LOG_WARNING);
-                throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn));
-            }
-        }
-
-        /* Start TLS if we're using it. */
-        if (!empty($this->_params['tls'])) {
-            if (!@ldap_start_tls($conn)) {
-                Horde::logMessage(
-                    sprintf('STARTTLS failed: [%d] %s',
-                            @ldap_errno($this->_ds),
-                            @ldap_error($this->_ds)),
-                    __FILE__, __LINE__, PEAR_LOG_ERR);
-            }
-        }
-
-        /* If necessary, bind to the LDAP server as the user with search
-         * permissions. */
-        if (!empty($this->_params['searchdn'])) {
-            $bind = ldap_bind($conn, $this->_params['searchdn'],
-                              $this->_params['searchpw']);
-            if ($bind === false) {
-                Horde::logMessage(
-                    sprintf('Bind to server %s:%d with DN %s failed: [%d] %s',
-                            $this->_params['hostspec'],
-                            $this->_params['port'],
-                            $this->_params['searchdn'],
-                            @ldap_errno($conn),
-                            @ldap_error($conn)),
-                    __FILE__, __LINE__, PEAR_LOG_ERR);
-                throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn));
-            }
-        }
-
-        /* Store the connection handle at the instance level. */
-        $this->_LDAP = $conn;
-    }
-
-}
diff --git a/lib/Driver/Sql.php b/lib/Driver/Sql.php
deleted file mode 100644 (file)
index 8eaac44..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-<?php
-
-class Shout_Driver_Sql extends Shout_Driver
-{
-    /**
-     * Handle for the current database connection.
-     * @var object $_db
-     */
-    protected $_db = null;
-
-    /**
-     * Boolean indicating whether or not we're connected to the LDAP
-     * server.
-     * @var boolean $_connected
-     */
-    protected $_connected = false;
-
-
-    /**
-    * Constructs a new Shout LDAP driver object.
-    *
-    * @param array  $params    A hash containing connection parameters.
-    */
-    function __construct($params = array())
-    {
-        parent::__construct($params);
-        $this->_connect();
-    }
-
-    public function getContexts()
-    {
-        $this->_connect();
-
-        $sql = 'SELECT context FROM %s';
-        $sql = sprintf($sql, $this->_params['table']);
-        $vars = array();
-
-        $result = $this->_db->query($sql, $vars);
-        if ($result instanceof PEAR_Error) {
-            throw Shout_Exception($result);
-        }
-
-        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
-        if ($row instanceof PEAR_Error) {
-            throw Shout_Exception($row);
-        }
-
-        $contexts = array();
-        while ($row && !($row instanceof PEAR_Error)) {
-            /* Add this new foo to the $_foo list. */
-            $contexts[] = $row['context'];
-
-            /* Advance to the new row in the result set. */
-            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
-        }
-
-         $result->free();
-         return $contexts;
-    }
-
-    /**
-     * Get a list of devices for a given context
-     *
-     * @param string $context    Context in which to search for devicess
-     *
-     * @return array  Array of devices within this context with their information
-     *
-     * @access private
-     */
-    public function getDevices($context)
-    {
-        $sql = 'SELECT id, name, alias, callerid, context, mailbox, host, permit, ' .
-               'nat, secret, disallow, allow FROM %s WHERE accountcode = ?';
-        $sql = sprintf($sql, $this->_params['table']);
-        $args = array($context);
-        $result = $this->_db->query($sql, $args);
-        if ($result instanceof PEAR_Error) {
-            throw new Shout_Exception($result);
-        }
-
-        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
-        if ($row instanceof PEAR_Error) {
-            throw new Shout_Exception($row);
-        }
-
-        $devices = array();
-        while ($row && !($row instanceof PEAR_Error)) {
-            // Asterisk uses the "name" field to indicate the registration
-            // identifier.  We use the field "alias" to put a friendly name on
-            // the device.  Thus devid -> name and name => alias
-            $devid = $row['name'];
-            $row['devid'] = $devid;
-            $row['name'] = $row['alias'];
-            unset($row['alias']);
-
-            // Trim off the context from the mailbox number
-            list($row['mailbox']) = explode('@', $row['mailbox']);
-
-            // Hide the DB internal ID from the front-end
-            unset($row['id']);
-
-            $devices[$devid] = $row;
-
-            /* Advance to the new row in the result set. */
-            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
-        }
-
-        $result->free();
-        return $devices;
-    }
-
-    /**
-     * Save a device (add or edit) to the backend.
-     *
-     * @param string $context  The context in which this device is valid
-     * @param array $info      Array of device details
-     */
-    public function saveDevice($context, $info)
-    {
-        // See getDevices() for an explanation of these conversions
-        $info['alias'] = $info['name'];
-        $info['mailbox'] = $info['mailbox'] . '@' . $context;
-
-        if ($info['devid']) {
-            // This is an edit
-            $info['name'] = $info['devid'];
-            $sql = 'UPDATE %s SET ';
-        } else {
-            // This is an add.  Generate a new unique ID and secret
-            $devid = $context . uniqid();
-            $secret = md5(uniqid(mt_rand));
-            $sql = 'INSERT INTO %s (name, accountcode, callerid, mailbox, ' .
-                   'secret, alias, canreinvite, nat, type) ' .
-                   ' VALUES (?, ?, ?, ?, ?, ?, "no", "yes", "peer")';
-
-        }
-
-
-    }
-
-    /**
-     * Get a list of users valid for the contexts
-     *
-     * @param string $context Context on which to search
-     *
-     * @return array User information indexed by voice mailbox number
-     */
-    public function getExtensions($context)
-    {
-        throw new Shout_Exception("Not implemented yet.");
-    }
-
-    /**
-     * Save a user to the LDAP tree
-     *
-     * @param string $context Context to which the user should be added
-     *
-     * @param string $extension Extension to be saved
-     *
-     * @param array $userdetails Phone numbers, PIN, options, etc to be saved
-     *
-     * @return TRUE on success, PEAR::Error object on error
-     */
-    public function saveExtension($context, $extension, $userdetails)
-    {
-        throw new Shout_Exception("Not implemented.");
-    }
-
-    /**
-     * Deletes a user from the LDAP tree
-     *
-     * @param string $context Context to delete the user from
-     * @param string $extension Extension of the user to be deleted
-     *
-     * @return boolean True on success, PEAR::Error object on error
-     */
-    public function deleteExtension($context, $extension)
-    {
-        throw new Shout_Exception("Not implemented.");
-    }
-
-    /**
-     * Attempts to open a persistent connection to the SQL server.
-     *
-     * @throws Horde_Exception
-     */
-    protected function _connect()
-    {
-        if ($this->_connected) {
-            return;
-        }
-
-        Horde::assertDriverConfig($this->_params, $this->_params['class'],
-                                  array('phptype', 'charset', 'table'));
-
-        if (!isset($this->_params['database'])) {
-            $this->_params['database'] = '';
-        }
-        if (!isset($this->_params['username'])) {
-            $this->_params['username'] = '';
-        }
-        if (!isset($this->_params['hostspec'])) {
-            $this->_params['hostspec'] = '';
-        }
-
-        /* Connect to the SQL server using the supplied parameters. */
-        $this->_write_db = DB::connect($this->_params,
-                                       array('persistent' => !empty($this->_params['persistent'])));
-        if ($this->_write_db instanceof PEAR_Error) {
-            throw Horde_Exception($this->_write_db);
-        }
-
-        // Set DB portability options.
-        switch ($this->_write_db->phptype) {
-        case 'mssql':
-            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
-            break;
-
-        default:
-            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
-        }
-
-        /* Check if we need to set up the read DB connection seperately. */
-        if (!empty($this->_params['splitread'])) {
-            $params = array_merge($this->_params, $this->_params['read']);
-            $this->_db = DB::connect($params,
-                                     array('persistent' => !empty($params['persistent'])));
-            if ($this->_db instanceof PEAR_Error) {
-                throw Horde_Exception($this->_db);
-            }
-
-            // Set DB portability options.
-            switch ($this->_db->phptype) {
-            case 'mssql':
-                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
-                break;
-
-            default:
-                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
-            }
-
-        } else {
-            /* Default to the same DB handle for the writer too. */
-            $this->_db = $this->_write_db;
-        }
-
-        $this->_connected = true;
-    }
-
-    /**
-     * Disconnects from the SQL server and cleans up the connection.
-     */
-    protected function _disconnect()
-    {
-        if ($this->_connected) {
-            $this->_connected = false;
-            $this->_db->disconnect();
-            $this->_write_db->disconnect();
-        }
-    }
-    
-}
diff --git a/lib/Exception.php b/lib/Exception.php
deleted file mode 100644 (file)
index 301bcf7..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?php
-class Shout_Exception extends Horde_Exception
-{
-}
\ No newline at end of file
diff --git a/lib/Forms/DeviceForm.php b/lib/Forms/DeviceForm.php
deleted file mode 100644 (file)
index 951738c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-/**
- * $Id: ExtensionForm.php 502 2009-12-21 04:01:12Z bklang $
- *
- * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- *
- * @package Shout
- */
-
-class DeviceDetailsForm extends Horde_Form {
-
-    function __construct(&$vars)
-    {
-        global $shout_extensions;
-
-        if ($vars->exists('devid')) {
-            $formtitle = "Edit Device";
-            $devid = $vars->get('devid');
-            $edit = true;
-        } else {
-            $formtitle = "Add Device";
-            $edit = false;
-        }
-
-        parent::__construct($vars, _("$formtitle - Context: $context"));
-        $this->addHidden('', 'action', 'text', true);
-        $vars->set('action', 'save');
-        if ($edit) {
-            $this->addHidden('', 'devid', 'text', true);
-
-        }
-        $this->addVariable(_("Device Name"), 'name', 'text', false);
-        $this->addVariable(_("Mailbox"), 'mailbox', 'int', false);
-        $this->addVariable(_("CallerID"), 'callerid', 'text', false);
-
-
-        return true;
-    }
-
-}
\ No newline at end of file
diff --git a/lib/Forms/ExtensionForm.php b/lib/Forms/ExtensionForm.php
deleted file mode 100644 (file)
index 444319d..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- *
- * @package Shout
- */
-
-class ExtensionDetailsForm extends Horde_Form {
-
-    /**
-     * ExtensionDetailsForm constructor.
-     * 
-     * @global <type> $shout_extensions
-     * @param <type> $vars
-     * @return <type> 
-     */
-    function __construct(&$vars)
-    {
-        global $shout_extensions;
-        $context = $vars->get('context');
-        if ($vars->exists('extension')) {
-            $formtitle = "Edit User";
-            $extension = $vars->get('extension');
-        } else {
-            $formtitle = "Add User";
-        }
-
-        parent::__construct($vars, _("$formtitle - Context: $context"));
-        $this->addHidden('', 'action', 'text', true);
-        $vars->set('action', 'save');
-        $this->addHidden('', 'extension', 'int', true);
-        $vars->set('newextension', $extension);
-        $this->addVariable(_("Full Name"), 'name', 'text', true);
-        $this->addVariable(_("Extension"), 'newextension', 'int', true);
-        $this->addVariable(_("E-Mail Address"), 'email', 'email', true);
-        $this->addVariable(_("Pager E-Mail Address"), 'pageremail', 'email', false);
-        $this->addVariable(_("PIN"), 'mailboxpin', 'int', true);
-
-        return true;
-    }
-
-    /**
-     * Process this form, saving its information to the backend.
-     *
-     * @param string $context  Context in which to execute this save
-     * FIXME: is there a better way to get the $context and $shout_extensions?
-     */
-    function execute($context)
-    {
-        global $shout_extensions;
-
-        $extension = $this->vars->get('extension');
-
-        # FIXME: Input Validation (Text::??)
-        $details = array(
-            'newextension' => $vars->get('newextension'),
-            'name' => $vars->get('name'),
-            'mailboxpin' => $vars->get('mailboxpin'),
-            'email' => $vars->get('email'),
-        );
-
-        $res = $shout_extensions->saveExtension($context, $extension, $details);
-    }
-
-}
\ No newline at end of file
diff --git a/lib/Shout.php b/lib/Shout.php
deleted file mode 100644 (file)
index 8eed4aa..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-/**
- * Shout:: defines an set of classes for the Shout application.
- *
- * $Id$
- *
- * Copyright 2005 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author  Ben Klang <ben@alkaloid.net>
- * @version $Revision: 94 $
- * @since   Shout 0.1
- * @package Shout
- */
-
-class Shout
-{
-    var $applist = array();
-    var $_applist_curapp = '';
-    var $_applist_curfield = '';
-
-    /**
-     * Build Shout's list of menu items.
-     *
-     * @access public
-     */
-    static public function getMenu($returnType = 'object')
-    {
-        global $conf, $context, $section, $action;
-
-        require_once 'Horde/Menu.php';
-
-        $menu = new Horde_Menu(HORDE_MENU_MASK_ALL);
-
-        $menu->add(Horde::applicationUrl('extensions.php'), _("Extensions"), "user.png");
-        $menu->add(Horde::applicationUrl('devices.php'), _("Devices"), "shout.png");
-        $menu->add(Horde::applicationUrl('routes.php'), _("Call Paths"));
-
-
-        if ($returnType == 'object') {
-            return $menu;
-        } else {
-            return $menu->render();
-        }
-    }
-
-    /**
-     * Generate the tabs at the top of each Shout pages
-     *
-     * @param &$vars Reference to the passed in variables
-     *
-     * @return object Horde_UI_Tabs
-     */
-    static public function getTabs($context, &$vars)
-    {
-        global $shout;
-        $perms = Horde_Perms::singleton();
-
-        $permprefix = 'shout:contexts:' . $context;
-
-        $tabs = new Horde_UI_Tabs('section', $vars);
-
-        if (Shout::checkRights($permprefix . ':extensions', null, 1)) {
-            $url = Horde::applicationUrl('extensions.php');
-            $tabs->addTab(_("_Extensions"), $url, 'extensions');
-        }
-
-        if (Shout::checkRights($permprefix . ':dialplan', null, 1)) {
-            $url = Horde::applicationUrl('dialplan.php');
-            $tabs->addTab(_("_Automated Attendant"), $url, 'dialplan');
-        }
-
-        if (Shout::checkRights($permprefix . ':conference', null, 1)) {
-            $url = Horde::applicationUrl('conference.php');
-            $tabs->addTab(_("_Conference Rooms"), $url, 'conference');
-        }
-
-       if (Shout::checkRights($permprefix . ':moh', null, 1)) {
-            $url = Horde::applicationUrl('moh.php');
-            $tabs->addTab(_("_Music on Hold"), $url, 'moh');
-        }
-
-        return $tabs;
-    }
-
-    /**
-     * Checks for the given permissions for the current user on the given
-     * permission.  Optionally check for higher-level permissions and ultimately
-     * test for superadmin priveleges.
-     *
-     * @param string $permname Name of the permission to check
-     *
-     * @param optional int $permmask Bitfield of permissions to check for
-     *
-     * @param options int $numparents Check for the same permissions this
-     *                                many levels up the tree
-     *
-     * @return boolean the effective permissions for the user.
-     */
-    static public function checkRights($permname, $permmask = null, $numparents = 0)
-    {
-        if (Horde_Auth::isAdmin()) { return true; }
-
-        $perms = Horde_Perms::singleton();
-        if ($permmask === null) {
-            $permmask = PERMS_SHOW|PERMS_READ;
-        }
-
-        # Default deny all permissions
-        $user = 0;
-        $superadmin = 0;
-
-        $superadmin = $perms->hasPermission('shout:superadmin',
-            Horde_Auth::getAuth(), $permmask);
-
-        while ($numparents >= 0) {
-            $tmpuser = $perms->hasPermission($permname,
-                Horde_Auth::getAuth(), $permmask);
-
-            $user = $user | $tmpuser;
-            if ($numparents > 0) {
-                $pos = strrpos($permname, ':');
-                if ($pos) {
-                    $permname = substr($permname, 0, $pos);
-                }
-            }
-            $numparents--;
-        }
-        $test = $superadmin | $user;
-$ret = ($test & $permmask) == $permmask;
-print "Shout::checkRights() returning $ret";
-        return ($test & $permmask) == $permmask;
-    }
-}
diff --git a/lib/api.php b/lib/api.php
deleted file mode 100644 (file)
index ce6d59f..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-/**
- * Shout external API interface.
- *
- * $Id$
- *
- * This file defines Shout's external API interface. Other
- * applications can interact with Shout through this API.
- *
- * @package Shout
- */
-@define('SHOUT_BASE', dirname(__FILE__) . "/..");
-
-$_services['perms'] = array(
-    'args' => array(),
-    'type' => '{urn:horde}stringArray',
-);
-
-$_services['attributes'] = array(
-    'args' => array(),
-    'type' => '{urn:horde}stringArray',
-);
-
-function _shout_perms()
-{
-    static $perms = array();
-    if (!empty($perms)) {
-        return $perms;
-    }
-
-    @define('SHOUT_BASE', dirname(__FILE__) . '/..');
-    require_once SHOUT_BASE . '/lib/base.php';
-
-    $perms['tree']['shout']['superadmin'] = false;
-    $perms['title']['shout:superadmin'] = _("Super Administrator");
-
-    $contexts = $shout->getContexts();
-
-    $perms['tree']['shout']['contexts'] = false;
-    $perms['title']['shout:contexts'] = _("Contexts");
-
-    // Run through every contact source.
-    foreach ($contexts as $context => $contextInfo) {
-        $perms['tree']['shout']['contexts'][$context] = false;
-        $perms['title']['shout:contexts:' . $context] = $context;
-
-        foreach(
-            array(
-                'users' => 'Users',
-                'dialplan' => 'Dialplan',
-                'moh' => 'Music on Hold',
-                'conferences' => 'Conferencing',
-            )
-            as $module => $modname) {
-            $perms['tree']['shout']['contexts'][$context][$module] = false;
-            $perms['title']["shout:contexts:$context:$module"] = $modname;
-        }
-    }
-
-//     function _shout_getContexts($searchfilters = SHOUT_CONTEXT_ALL,
-//                          $filterperms = null)
-
-    return $perms;
-}
-
-function _shout_attributes()
-{
-    // See CONGREGATION_BASE/docs/api.txt for information on the structure
-    // of this array.
-    $shoutAttributes = array(
-        'description' => 'Phone System User Settings',
-        'attributes' => array(
-            'extension' => array(
-                'name' => 'Extension',
-                'description' => 'Phone System Extension (doubles as Voice Mailbox Number',
-                'type' => 'int',
-                'size' => 3,
-                'keys' => array(
-                    'ldap' => 'asteriskVoiceMailbox',
-                ),
-                'limit' => 1,
-                'required' => true,
-                'infoset' => 'basic',
-            ),
-
-            'mailboxpin' => array(
-                'name' => 'Mailbox PIN',
-                'description' => 'Voice Mailbox PIN',
-                'type' => 'int',
-                'size' => 12,
-                'keys' => array(
-                    'ldap' => 'asteriskVoiceMailboxPIN',
-                ),
-                'limit' => 1,
-                'required' => true,
-                'infoset' => 'basic',
-            ),
-
-            'phonenumbers' => array(
-                'name' => 'Telephone Numbers',
-                'description' => 'Dialout phone numbers',
-                'type' => 'cellphone', // WHY does Horde have cellphone but NOT
-                                       // telephone or just phonenumber???
-                'size' => 12,
-                'keys' => array(
-                    'ldap' => 'telephoneNumber',
-                ),
-                'limit' => 5,
-                'required' => true,
-                'infoset' => 'basic',
-            ),
-
-            'dialstring' => array(
-                'name' => 'Dial String',
-                'description' => 'Asterisk raw dial string',
-                'type' => 'cellphone', // WHY does Horde have cellphone but NOT
-                                       // telephone or just phonenumber???
-                'size' => 12,
-                'keys' => array(
-                    'ldap' => 'telephoneNumber',
-                ),
-                'limit' => 5,
-                'required' => true,
-                'infoset' => 'restricted',
-            ),
-        ),
-    );
-
-    return $shoutAttributes;
-}
diff --git a/lib/base.php b/lib/base.php
deleted file mode 100644 (file)
index 4ec1df0..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-/**
- * Shout base inclusion file.
- *
- * $Id$
- *
- * This file brings in all of the dependencies that every Shout
- * script will need and sets up objects that all scripts use.
- */
-
-if (!defined('SHOUT_BASE')) {
-    define('SHOUT_BASE', dirname(__FILE__). '/..');
-}
-
-if (!defined('HORDE_BASE')) {
-    /* If horde does not live directly under the app directory, the HORDE_BASE
-     * constant should be defined in config/horde.local.php. */
-    if (file_exists(SHOUT_BASE. '/config/horde.local.php')) {
-        include SHOUT_BASE . '/config/horde.local.php';
-    } else {
-        define('HORDE_BASE', SHOUT_BASE . '/..');
-    }
-}
-
-// Load the Horde Framework core, and set up inclusion paths.
-require_once HORDE_BASE . '/lib/core.php';
-
-$registry = &Horde_Registry::singleton();
-try {
-    $registry->pushApp('shout', array('check_perms' => true, 'logintasks' => true));
-} catch (Horde_Exception $e) {
-    Horde::authenticationFailureRedirect('shout', $e);
-}
-
-$conf = &$GLOBALS['conf'];
-@define('SHOUT_TEMPLATES', $registry->get('templates'));
-
-// Ensure Shout is properly configured before use
-$shout_configured = (@is_readable(SHOUT_BASE . '/config/conf.php'));
-if (!$shout_configured) {
-    Horde_Test::configFilesMissing('Shout', SHOUT_BASE, array('conf.php'));
-}
-
-$notification = Horde_Notification::singleton();
-$notification->attach('status');
-
-//// Shout base libraries.
-//require_once SHOUT_BASE . '/lib/Shout.php';
-//require_once SHOUT_BASE . '/lib/Driver.php';
-//
-//// Form libraries.
-//require_once 'Horde/Form.php';
-//require_once 'Horde/Form/Renderer.php';
-//
-//// Variable handling libraries
-//require_once 'Horde/Variables.php';
-//require_once 'Horde/Text/Filter.php';
-//
-//// UI classes.
-//require_once 'Horde/UI/Tabs.php';
-
-$shout_storage = Shout_Driver::factory('storage');
-$shout_extensions = Shout_Driver::factory('extensions');
-$shout_devices = Shout_Driver::factory('devices');
-
-$context = Horde_Util::getFormData('context');
-$section = Horde_Util::getFormData('section');
-
-try {
-    $contexts = $shout_storage->getContexts();
-} catch (Shout_Exception $e) {
-    $notification->push($e);
-    $contexts = false;
-}
-
-if (count($contexts) == 1) {
-    // Default to the user's only context
-    $context = $contexts[0];
-} elseif (!empty($context) && !in_array($context, $contexts)) {
-    $notification->push('You do not have permission to access that context.', 'horde.error');
-    $context = false;
-} elseif (!empty($context)) {
-    $notification->push("Please select a context to continue.", 'horde.info');
-    $context = false;
-}
-
-$_SESSION['shout']['context'] = $context;
\ No newline at end of file
diff --git a/lib/shoutAtts.php b/lib/shoutAtts.php
deleted file mode 100644 (file)
index cc70a90..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/* 
- *
- * Copyright 2005 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- */
-
-$shoutAttributes = array();
-$shoutAttributes['description'] = 'Phone System Options';
-$shoutAttributes['permsNode'] = 'shout:contexts:somethingOrOther';
-$shoutAttributes['attributes']['extension']['description'] = 'Internal Phone Extension';
-$shoutAttributes['attributes']['extension']['type'] = 'integer';
-$shoutAttributes['attributes']['extension']['size'] = 3; // max length for this particular string
-$shoutAttributes['attributes']['extension']['ldapKey'] = 'voiceextensionsomethingIhavenoidea';
-
-$shoutAttributes['attributes']['mailboxpin']['description'] = 'Mailbox PIN';
-$shoutAttributes['attributes']['mailboxpin']['type'] = 'integer';
-$shoutAttributes['attributes']['mailboxpin']['size'] = 4;
-$shoutAttributes['attributes']['mailboxpin']['ldapKey'] = 'voicemailboxpin';
-
-$shoutAttributes['attributes']['phonenumbers']['description'] = 'Phone Numbers';
-$shoutAttributes['attributes']['phonenumbers']['type'] = 'array';
-$shoutAttributes['attributes']['phonenumbers']['size'] = 1;
-$shoutAttributes['attributes']['phonenumbers']['arrayType'] = 'string';
-$shoutAttributes['attributes']['phonenumbers']['ldapKey'] = 'asteriskuserphonenumbers';
-
-?>
diff --git a/lib/version.php b/lib/version.php
deleted file mode 100644 (file)
index b855f6a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<?php define('SHOUT_VERSION', '0.1-svn') ?>
\ No newline at end of file
diff --git a/moh.php b/moh.php
deleted file mode 100644 (file)
index 3c89ebd..0000000
--- a/moh.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @package shout
- */
-if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
-    header('Location: /');
-    exit();
-}
-
-$title = _('Music on Hold');
-
-require SHOUT_TEMPLATES . '/common-header.inc';
-require SHOUT_TEMPLATES . '/menu.inc';
-
-$notification->notify();
-
-$tabs->render($section);
-
-require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/security.php b/security.php
deleted file mode 100644 (file)
index 11f3126..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @package shout
- */
-
-if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
-    header('Location: /');
-    exit();
-}
-
-$title = _('Security');
-
-require SHOUT_TEMPLATES . '/common-header.inc';
-require SHOUT_TEMPLATES . '/menu.inc';
-
-$notification->notify();
-
-$tabs->render($section);
-
-Shout::getApplist();
-
-require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/shout/TODO b/shout/TODO
new file mode 100644 (file)
index 0000000..c53e828
--- /dev/null
@@ -0,0 +1 @@
+* Convert forms to Beatnik style associative arrays. 2006-01-26 bklang
diff --git a/shout/config/applist.xml.dist b/shout/config/applist.xml.dist
new file mode 100644 (file)
index 0000000..07f72e4
--- /dev/null
@@ -0,0 +1,862 @@
+<?xml version="1.0"?>
+<asterisk type="applist">    <application name="SayAlpha">
+        <synopsis>Say Alpha</synopsis>
+        <usage>  SayAlpha(string): Spells the passed string
+
+</usage>
+    </application>
+    <application name="TxFAX">
+        <synopsis>Send a FAX file</synopsis>
+        <usage>  TxFAX(filename[|option]):  Send a given file name as a FAX. Returns -1
+Uses LOCALSTATIONID to identify itself to the remote end.
+Sets REMOTESTATIONID to the receiver CSID.
+Returns -1 when the user hangs up, or if the file does not exist.
+Returns 0 otherwise.
+
+</usage>
+    </application>
+    <application name="VoiceMailMain">
+        <synopsis>Enter voicemail system</synopsis>
+        <usage>  VoiceMailMain([[s]mailbox][@context]): Enters the main voicemail system
+for the checking of voicemail.  The mailbox can be passed as the option,
+which will stop the voicemail system from prompting the user for the mailbox.
+If the mailbox is preceded by &#39;s&#39; then the password check will be skipped.  If
+the mailbox is preceded by &#39;p&#39; then the supplied mailbox is prepended to the
+user&#39;s entry and the resulting string is used as the mailbox number.  This is
+useful for virtual hosting of voicemail boxes.  If a context is specified,
+logins are considered in that voicemail context only.
+Returns -1 if the user hangs up or 0 otherwise.
+
+</usage>
+    </application>
+    <application name="StripLSD">
+        <synopsis>Strip Least Significant Digits</synopsis>
+        <usage>  StripLSD(count): Strips the trailing  &#39;count&#39;  digits  from  the  channel&#39;s
+associated extension. For example, the  number  5551212 when stripped with a
+count of 4 would be changed to 555.  This app always returns 0, and the PBX
+will continue processing at the next priority for the *new* extension.
+  So, for  example, if  priority 3 of 5551212  is  StripLSD 4, the next step
+executed will be priority 4 of 555.  If you switch into an  extension which
+has no first step, the PBX will treat it as though the user dialed an
+invalid extension.
+
+</usage>
+    </application>
+    <application name="ChangeMonitor">
+        <synopsis>Change monitoring filename of a channel</synopsis>
+        <usage>ChangeMonitor(filename_base)
+Changes monitoring filename of a channel. Has no effect if the channel is not monitored
+The argument is the new filename base to use for monitoring this channel.
+
+</usage>
+    </application>
+    <application name="BackgroundDetect">
+        <synopsis>Background a file with talk detect</synopsis>
+        <usage>  BackgroundDetect(filename[|sil[|min|[max]]]):  Plays  back  a  given
+filename, waiting for interruption from a given digit (the digit must
+start the beginning of a valid extension, or it will be ignored).
+During the playback of the file, audio is monitored in the receive
+direction, and if a period of non-silence which is greater than &#39;min&#39; ms
+yet less than &#39;max&#39; ms is followed by silence for at least &#39;sil&#39; ms then
+the audio playback is aborted and processing jumps to the &#39;talk&#39; extension
+if available.  If unspecified, sil, min, and max default to 1000, 100, and
+infinity respectively.  Returns -1 on hangup, and 0 on successful playback
+completion with no exit conditions.
+
+</usage>
+    </application>
+    <application name="SayNumber">
+        <synopsis>Say Number</synopsis>
+        <usage>  SayNumber(digits[,gender]): Says the passed number. SayNumber is using
+the current language setting for the channel. (See app SetLanguage).
+
+</usage>
+    </application>
+    <application name="Directory">
+        <synopsis>Provide directory of voicemail extensions</synopsis>
+        <usage>  Directory(vm-context[|dial-context[|options]]): Presents the user with a directory
+of extensions from which they  may  select  by name. The  list  of  names 
+and  extensions  is discovered from  voicemail.conf. The  vm-context  argument
+is required, and specifies  the  context  of voicemail.conf to use.  The
+dial-context is the context to use for dialing the users, and defaults to
+the vm-context if unspecified. The &#39;f&#39; option causes the directory to match
+based on the first name in voicemail.conf instead of the last name.
+Returns 0 unless the user hangs up. It  also sets up the channel on exit
+to enter the extension the user selected.
+
+</usage>
+    </application>
+    <application name="SetCallerID">
+        <synopsis>Set CallerID</synopsis>
+        <usage>  SetCallerID(clid[|a]): Set Caller*ID on a call to a new
+value.  Sets ANI as well if a flag is used.  Always returns 0
+
+</usage>
+    </application>
+    <application name="Congestion">
+        <synopsis>Indicate congestion and stop</synopsis>
+        <usage>  Congestion([timeout]): Requests that the channel indicate congestion
+and then waits for the user to hang up or for the optional timeout to
+expire.  Always returns -1.
+</usage>
+    </application>
+    <application name="Busy">
+        <synopsis>Indicate busy condition and stop</synopsis>
+        <usage>  Busy([timeout]): Requests that the channel indicate busy condition and
+then waits for the user to hang up or the optional timeout to expire.
+Always returns -1.
+</usage>
+    </application>
+    <application name="Park">
+        <synopsis>Park yourself</synopsis>
+        <usage>Park(exten):Used to park yourself (typically in combination with a supervised
+transfer to know the parking space.  This Application is always
+registered internally and does not need to be explicitly added
+into the dialplan, although you should include the &#39;parkedcalls&#39;
+context.
+
+</usage>
+    </application>
+    <application name="VoiceMail">
+        <synopsis>Leave a voicemail message</synopsis>
+        <usage>  VoiceMail([s|u|b]extension[@context][&amp;extension[@context]][...]):  Leavesvoicemail for a given extension (must be configured in voicemail.conf).
+ If the extension is preceded by 
+* &#39;s&#39; then instructions for leaving the message will be skipped.
+* &#39;u&#39; then the &quot;unavailable&quot; message will be played.
+  (/var/lib/asterisk/sounds/vm/&lt;exten&gt;/unavail) if it exists.
+* &#39;b&#39; then the the busy message will be played (that is, busy instead of unavail).
+If the caller presses &#39;0&#39; (zero) during the prompt, the call jumps to
+extension &#39;o&#39; in the current context.
+If the caller presses &#39;*&#39; during the prompt, the call jumps to
+extension &#39;a&#39; in the current context.
+If the requested mailbox does not exist, and there exists a priority
+n + 101, then that priority will be taken next.
+When multiple mailboxes are specified, the unavailable or busy message
+will be taken from the first mailbox specified.
+Returns -1 on error or mailbox not found, or if the user hangs up.
+Otherwise, it returns 0.
+
+</usage>
+    </application>
+    <application name="Wait">
+        <synopsis>Waits for some time</synopsis>
+        <usage>  Wait(seconds): Waits for a specified number of seconds, then returns 0.
+seconds can be passed with fractions of a second. (eg: 1.5 = 1.5 seconds)
+
+</usage>
+    </application>
+    <application name="CallingPres">
+        <synopsis>Change the presentation for the callerid</synopsis>
+        <usage>Callingpres(number): Changes the presentation for the callerid. Should be called before placing an outgoing call
+
+</usage>
+    </application>
+    <application name="MusicOnHold">
+        <synopsis>Play Music On Hold indefinitely</synopsis>
+        <usage>MusicOnHold(class): Plays hold music specified by class.  If omitted, the default
+music source for the channel will be used. Set the default 
+class with the SetMusicOnHold() application.
+Returns -1 on hangup.
+Never returns otherwise.
+
+</usage>
+    </application>
+    <application name="AbsoluteTimeout">
+        <synopsis>Set absolute maximum time of call</synopsis>
+        <usage>  AbsoluteTimeout(seconds): Set the absolute maximum amount of time permitted
+for a call.  A setting of 0 disables the timeout.  Always returns 0.
+
+</usage>
+    </application>
+    <application name="VoiceMailMain2">
+        <synopsis>Enter voicemail system</synopsis>
+        <usage>  VoiceMailMain([[s]mailbox][@context]): Enters the main voicemail system
+for the checking of voicemail.  The mailbox can be passed as the option,
+which will stop the voicemail system from prompting the user for the mailbox.
+If the mailbox is preceded by &#39;s&#39; then the password check will be skipped.  If
+the mailbox is preceded by &#39;p&#39; then the supplied mailbox is prepended to the
+user&#39;s entry and the resulting string is used as the mailbox number.  This is
+useful for virtual hosting of voicemail boxes.  If a context is specified,
+logins are considered in that voicemail context only.
+Returns -1 if the user hangs up or 0 otherwise.
+
+</usage>
+    </application>
+    <application name="BackGround">
+        <synopsis>Play a file while awaiting extension</synopsis>
+        <usage>  Background(filename[|options[|langoverride]]): Plays a given file, while simultaneously
+waiting for the user to begin typing an extension. The  timeouts do not
+count until the last BackGround application has ended.
+Options may also be  included following a pipe symbol. The &#39;skip&#39;
+option causes the playback of the message to  be  skipped  if  the  channel
+is not in the &#39;up&#39; state (i.e. it hasn&#39;t been  answered  yet. If &#39;skip&#39; is 
+specified, the application will return immediately should the channel not be
+off hook.  Otherwise, unless &#39;noanswer&#39; is specified, the channel channel will
+be answered before the sound is played. Not all channels support playing
+messages while still hook. The &#39;langoverride&#39; may be a language to use for
+playing the prompt which differs from the current language of the channel
+Returns -1 if the channel was hung up, or if the file does not exist. 
+Returns 0 otherwise.
+
+</usage>
+    </application>
+    <application name="Verbose">
+        <synopsis>Send arbitrary text to verbose output</synopsis>
+        <usage>Verbose([&lt;level&gt;|]&lt;message&gt;)
+  level must be an integer value.  If not specified, defaults to 0.  Always returns 0.
+
+</usage>
+    </application>
+    <application name="StopMonitor">
+        <synopsis>Stop monitoring a channel</synopsis>
+        <usage>StopMonitor
+Stops monitoring a channel. Has no effect if the channel is not monitored
+
+</usage>
+    </application>
+    <application name="SubString">
+        <synopsis>(Deprecated) Save substring digits in a given variable</synopsis>
+        <usage>  (Deprecated, use ${variable:a:b} instead)
+
+  SubString(variable=string_of_digits|count1|count2): Assigns the substring
+of string_of_digits to a given variable. Parameter count1 may be positive
+or negative. If it&#39;s positive then we skip the first count1 digits from the
+left. If it&#39;s negative, we move count1 digits counting from the end of
+the string to the left. Parameter count2 implies how many digits we are
+taking from the point that count1 placed us. If count2 is negative, then
+that many digits are omitted from the end.
+For example:
+exten =&gt; _NXXXXXX,1,SubString,test=2564286161|0|3
+assigns the area code (3 first digits) to variable test.
+exten =&gt; _NXXXXXX,1,SubString,test=2564286161|-7|7
+assigns the last 7 digits to variable test.
+exten =&gt; _NXXXXXX,1,SubString,test=2564286161|0|-4
+assigns all but the last 4 digits to variable test.
+If there are no parameters it&#39;ll return with -1.
+If there wrong parameters it go on and return with 0
+
+</usage>
+    </application>
+    <application name="MeetMeCount">
+        <synopsis>MeetMe participant count</synopsis>
+        <usage>  MeetMeCount(confno[|var]): Plays back the number of users in the specifiedi
+MeetMe conference. If var is specified, playback will be skipped and the value
+will be returned in the variable. Returns 0 on success or -1 on a hangup.
+A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.
+
+</usage>
+    </application>
+    <application name="SetCIDName">
+        <synopsis>Set CallerID Name</synopsis>
+        <usage>  SetCIDName(cname[|a]): Set Caller*ID Name on a call to a new
+value, while preserving the original Caller*ID number.  This is
+useful for providing additional information to the called
+party. Sets ANI as well if a flag is used.  Always returns 0
+
+</usage>
+    </application>
+    <application name="Flash">
+        <synopsis>Flashes a Zap Trunk</synopsis>
+        <usage>  Flash(): Sends a flash on a zap trunk.  This is only a hack for
+people who want to perform transfers and such via AGI and is generally
+quite useless otherwise.  Returns 0 on success or -1 if this is not
+a zap trunk
+
+</usage>
+    </application>
+    <application name="Suffix">
+        <synopsis>Append trailing digits</synopsis>
+        <usage>  Suffix(digits): Appends the  digit  string  specified  by  digits to the
+channel&#39;s associated extension. For example, the number 555 when  suffixed
+with &#39;1212&#39; will become 5551212. This app always returns 0, and the PBX will
+continue processing at the next priority for the *new* extension.
+  So, for example, if priority  3  of  555 is Suffix 1212, the  next  step
+executed will be priority 4 of 5551212. If  you  switch  into an  extension
+which has no first step, the PBX will treat it as though the user dialed an
+invalid extension.
+
+</usage>
+    </application>
+    <application name="SetAccount">
+        <synopsis>Sets account code</synopsis>
+        <usage>  SetAccount([account]):  Set  the  channel account code for billing
+purposes. Always returns 0.
+
+</usage>
+    </application>
+    <application name="SayPhonetic">
+        <synopsis>Say Phonetic</synopsis>
+        <usage>  SayPhonetic(string): Spells the passed string with phonetic alphabet
+
+</usage>
+    </application>
+    <application name="Monitor">
+        <synopsis>Monitor a channel</synopsis>
+        <usage>Monitor([file_format|[fname_base]|[options]]):
+Used to start monitoring a channel. The channel&#39;s input and output
+voice packets are logged to files until the channel hangs up or
+monitoring is stopped by the StopMonitor application.
+  file_format          optional, if not set, defaults to &quot;wav&quot;
+  fname_base           if set, changes the filename used to the one specified.
+  options:
+    m   - when the recording ends mix the two leg files into one and
+          delete the two leg files.  If the variable MONITOR_EXEC is set, the
+          application referenced in it will be executed instead of
+          soxmix and the raw leg files will NOT be deleted automatically.
+          soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files
+          and a target mixed file name which is the same as the leg file names
+          only without the in/out designator.
+          If MONITOR_EXEC_ARGS is set, the contents will be passed on as
+          additional arguements to MONITOR_EXEC
+          Both MONITOR_EXEC and the Mix flag can be set from the
+          administrator interface
+
+    b   - Don&#39;t begin recording unless a call is bridged to another channel
+
+Returns -1 if monitor files can&#39;t be opened or if the channel is already
+monitored, otherwise 0.
+
+</usage>
+    </application>
+    <application name="WaitMusicOnHold">
+        <synopsis>Wait, playing Music On Hold</synopsis>
+        <usage>WaitMusicOnHold(delay): Plays hold music specified number of seconds.  Returns 0 when
+done, or -1 on hangup.  If no hold music is available, the delay will
+still occur with no sound.
+
+</usage>
+    </application>
+    <application name="Answer">
+        <synopsis>Answer a channel if ringing</synopsis>
+        <usage>  Answer(): If the channel is ringing, answer it, otherwise do nothing. 
+Returns 0 unless it tries to answer the channel and fails.
+
+</usage>
+    </application>
+    <application name="Echo">
+        <synopsis>Echo audio read back to the user</synopsis>
+        <usage>  Echo():  Echo audio read from channel back to the channel. Returns 0
+if the user exits with the &#39;#&#39; key, or -1 if the user hangs up.
+
+</usage>
+    </application>
+    <application name="SayDigits">
+        <synopsis>Say Digits</synopsis>
+        <usage>  SayDigits(digits): Says the passed digits. SayDigits is using the
+current language setting for the channel. (See app setLanguage)
+
+</usage>
+    </application>
+    <application name="MailboxExists">
+        <synopsis>Check if vmbox exists</synopsis>
+        <usage>  MailboxExists(mailbox[@context]): Conditionally branches to priority n+101
+if the specified voice mailbox exists.
+
+</usage>
+    </application>
+    <application name="Goto">
+        <synopsis>Goto a particular priority, extension, or context</synopsis>
+        <usage>  Goto([[context|]extension|]priority):  Set the  priority to the specified
+value, optionally setting the extension and optionally the context as well.
+The extension BYEXTENSION is special in that it uses the current extension,
+thus  permitting you to go to a different context, without specifying a
+specific extension. Always returns 0, even if the given context, extension,
+or priority is invalid.
+
+</usage>
+    </application>
+    <application name="MeetMe">
+        <synopsis>MeetMe conference bridge</synopsis>
+        <usage>  MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe conference.
+If the conference number is omitted, the user will be prompted to enter
+one. 
+MeetMe returns 0 if user pressed # to exit (see option &#39;p&#39;), otherwise -1.
+Please note: A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING TO WORK!
+
+The option string may contain zero or more of the following characters:
+      &#39;m&#39; -- set monitor only mode (Listen only, no talking)
+      &#39;t&#39; -- set talk only mode. (Talk only, no listening)
+      &#39;p&#39; -- allow user to exit the conference by pressing &#39;#&#39;
+      &#39;X&#39; -- allow user to exit the conference by entering a valid single
+             digit extension ${MEETME_EXIT_CONTEXT} or the current context
+             if that variable is not defined.
+      &#39;d&#39; -- dynamically add conference
+      &#39;D&#39; -- dynamically add conference, prompting for a PIN
+      &#39;e&#39; -- select an empty conference
+      &#39;E&#39; -- select an empty pinless conference
+      &#39;v&#39; -- video mode
+      &#39;q&#39; -- quiet mode (don&#39;t play enter/leave sounds)
+      &#39;M&#39; -- enable music on hold when the conference has a single caller
+      &#39;x&#39; -- close the conference when last marked user exits
+      &#39;w&#39; -- wait until the marked user enters the conference
+      &#39;b&#39; -- run AGI script specified in ${MEETME_AGI_BACKGROUND}
+         Default: conf-background.agi
+        (Note: This does not work with non-Zap channels in the same conference)
+      &#39;s&#39; -- Present menu (user or admin) when &#39;*&#39; is received (&#39;send&#39; to menu)
+      &#39;a&#39; -- set admin mode
+      &#39;A&#39; -- set marked mode
+
+</usage>
+    </application>
+    <application name="SetGlobalVar">
+        <synopsis>Set global variable to value</synopsis>
+        <usage>  SetGlobalVar(#n=value): Sets global variable n to value. Global
+variable are available across channels.
+
+</usage>
+    </application>
+    <application name="SoftHangup">
+        <synopsis>Soft Hangup Application</synopsis>
+        <usage>  SoftHangup(Technology/resource)
+Hangs up the requested channel.  Always returns 0
+
+</usage>
+    </application>
+    <application name="SetCDRUserField">
+        <synopsis>Set the CDR user field</synopsis>
+        <usage>[Synopsis]
+SetCDRUserField(value)
+
+[Description]
+SetCDRUserField(value): Set the CDR &#39;user field&#39; to value
+       The Call Data Record (CDR) user field is an extra field you
+       can use for data not stored anywhere else in the record.
+       CDR records can be used for billing or storing other arbitrary data
+       (I.E. telephone survey responses)
+       Also see AppendCDRUserField().
+       Always returns 0
+
+</usage>
+    </application>
+    <application name="Ringing">
+        <synopsis>Indicate ringing tone</synopsis>
+        <usage>  Ringing(): Request that the channel indicate ringing tone to the user.
+Always returns 0.
+
+</usage>
+    </application>
+    <application name="System">
+        <synopsis>Execute a system command</synopsis>
+        <usage>  System(command): Executes a command  by  using  system(). Returns -1 on
+failure to execute the specified command. If  the command itself executes
+but is in error, and if there exists a priority n + 101, where &#39;n&#39; is the
+priority of the current instance, then  the  channel  will  be  setup  to
+continue at that priority level.  Otherwise, System returns 0.
+
+</usage>
+    </application>
+    <application name="VoiceMail2">
+        <synopsis>Leave a voicemail message</synopsis>
+        <usage>  VoiceMail([s|u|b]extension[@context][&amp;extension[@context]][...]):  Leavesvoicemail for a given extension (must be configured in voicemail.conf).
+ If the extension is preceded by 
+* &#39;s&#39; then instructions for leaving the message will be skipped.
+* &#39;u&#39; then the &quot;unavailable&quot; message will be played.
+  (/var/lib/asterisk/sounds/vm/&lt;exten&gt;/unavail) if it exists.
+* &#39;b&#39; then the the busy message will be played (that is, busy instead of unavail).
+If the caller presses &#39;0&#39; (zero) during the prompt, the call jumps to
+extension &#39;o&#39; in the current context.
+If the caller presses &#39;*&#39; during the prompt, the call jumps to
+extension &#39;a&#39; in the current context.
+If the requested mailbox does not exist, and there exists a priority
+n + 101, then that priority will be taken next.
+When multiple mailboxes are specified, the unavailable or busy message
+will be taken from the first mailbox specified.
+Returns -1 on error or mailbox not found, or if the user hangs up.
+Otherwise, it returns 0.
+
+</usage>
+    </application>
+    <application name="DigitTimeout">
+        <synopsis>Set maximum timeout between digits</synopsis>
+        <usage>  DigitTimeout(seconds): Set the maximum amount of time permitted between
+digits when the user is typing in an extension. When this timeout expires,
+after the user has started to type in an extension, the extension will be
+considered complete, and will be interpreted. Note that if an extension
+typed in is valid, it will not have to timeout to be tested, so typically
+at the expiry of this timeout, the extension will be considered invalid
+(and thus control would be passed to the &#39;i&#39; extension, or if it doesn&#39;t
+exist the call would be terminated). The default timeout is 5 seconds.
+Always returns 0.
+
+</usage>
+    </application>
+    <application name="ZapScan">
+        <synopsis>Scan Zap channels to monitor calls</synopsis>
+        <usage>  ZapScan allows a call center manager to monitor Zap channels in
+a convenient way.  Use &#39;#&#39; to select the next channel and use &#39;*&#39; to exit
+
+</usage>
+    </application>
+    <application name="TrySystem">
+        <synopsis>Try executing a system command</synopsis>
+        <usage>  TrySystem(command): Executes a command  by  using  system(). Returns 0
+on any situation. If  the command itself executes but is in error, and if
+there exists a priority n + 101, where &#39;n&#39; is the priority of the current
+instance, then  the  channel  will  be  setup  to continue at that
+priority level.  Otherwise, System returns 0.
+
+</usage>
+    </application>
+    <application name="SetAMAFlags">
+        <synopsis>Sets AMA Flags</synopsis>
+        <usage>  SetAMAFlags([flag]):  Set  the  channel AMA Flags for billing
+purposes. Always returns 0.
+
+</usage>
+    </application>
+    <application name="WaitForRing">
+        <synopsis>Wait for Ring Application</synopsis>
+        <usage>  WaitForRing(timeout)
+Returns 0 after waiting at least timeout seconds. and
+only after the next ring has completed.  Returns 0 on
+success or -1 on hangup
+
+</usage>
+    </application>
+    <application name="Prefix">
+        <synopsis>Prepend leading digits</synopsis>
+        <usage>  Prefix(digits): Prepends the digit string specified by digits to the
+channel&#39;s associated extension. For example, the number 1212 when prefixed
+with &#39;555&#39; will become 5551212. This app always returns 0, and the PBX will
+continue processing at the next priority for the *new* extension.
+  So, for example, if priority  3  of 1212 is  Prefix  555, the next step
+executed will be priority 4 of 5551212. If you switch into an extension
+which has no first step, the PBX will treat it as though the user dialed an
+invalid extension.
+
+</usage>
+    </application>
+    <application name="AppendCDRUserField">
+        <synopsis>Append to the CDR user field</synopsis>
+        <usage>[Synopsis]
+AppendCDRUserField(value)
+
+[Description]
+AppendCDRUserField(value): Append value to the CDR user field
+       The Call Data Record (CDR) user field is an extra field you
+       can use for data not stored anywhere else in the record.
+       CDR records can be used for billing or storing other arbitrary data
+       (I.E. telephone survey responses)
+       Also see SetCDRUserField().
+       Always returns 0
+
+</usage>
+    </application>
+    <application name="TXTCIDName">
+        <synopsis>Lookup caller name from TXT record</synopsis>
+        <usage>  TXTLookup(CallerID):  Looks up a Caller Name via DNS and sets
+the variable &#39;TXTCIDNAME&#39;. TXTCIDName will either be blank
+or return the value found in the TXT record in DNS.
+
+</usage>
+    </application>
+    <application name="RxFAX">
+        <synopsis>Receive a FAX to a file</synopsis>
+        <usage>  RxFAX(filename): Receives a FAX from the channel into a
+given filename. If the file exists it will be overwritten. The file
+should be in TIFF/F format.
+Uses LOCALSTATIONID to identify itself to the remote end.
+     LOCALHEADERINFO to generate a header line on each page.
+Sets REMOTESTATIONID to the sender CSID.
+     FAXPAGES to the number of pages received.
+     FAXBITRATE to the transmition rate.
+     FAXRESOLUTION to the resolution.
+Returns -1 when the user hangs up.
+
+</usage>
+    </application>
+    <application name="Transfer">
+        <synopsis>Transfer caller to remote extension</synopsis>
+        <usage>  Transfer(exten):  Requests the remote caller be transferred to
+a given extension. Returns -1 on hangup, or 0 on completion
+regardless of whether the transfer was successful.  If the transfer
+was *not* supported or successful and there exists a priority n + 101,
+then that priority will be taken next.
+
+</usage>
+    </application>
+    <application name="SetMusicOnHold">
+        <synopsis>Set default Music On Hold class</synopsis>
+        <usage>SetMusicOnHold(class): Sets the default class for music on hold for a given channel.  When
+music on hold is activated, this class will be used to select which
+music is played.
+
+</usage>
+    </application>
+    <application name="MeetMeAdmin">
+        <synopsis>MeetMe conference Administration</synopsis>
+        <usage>  MeetMeAdmin(confno,command[,user]): Run admin command for conference
+      &#39;K&#39; -- Kick all users out of conference
+      &#39;k&#39; -- Kick one user out of conference
+      &#39;L&#39; -- Lock conference
+      &#39;l&#39; -- Unlock conference
+      &#39;M&#39; -- Mute conference
+      &#39;m&#39; -- Unmute conference
+
+</usage>
+    </application>
+    <application name="SetVar">
+        <synopsis>Set variable to value</synopsis>
+        <usage>  Setvar(#n=value): Sets channel specific variable n to value
+</usage>
+    </application>
+    <application name="Record">
+        <synopsis>Record to a file</synopsis>
+        <usage>  Record(filename:format|silence[|maxduration][|option])
+
+Records from the channel into a given filename. If the file exists it will
+be overwritten.
+- &#39;format&#39; is the format of the file type to be recorded (wav, gsm, etc).
+- &#39;silence&#39; is the number of seconds of silence to allow before returning.
+- &#39;maxduration&#39; is the maximum recording duration in seconds. If missing
+or 0 there is no maximum.
+- &#39;option&#39; may be &#39;skip&#39; to return immediately if the line is not up,
+or &#39;noanswer&#39; to attempt to record even if the line is not up.
+
+If filename contains &#39;%d&#39;, these characters will be replaced with a number
+incremented by one each time the file is recorded. 
+
+Formats: g723, g729, gsm, h263, ulaw, alaw, vox, wav, WAV
+
+User can press &#39;#&#39; to terminate the recording and continue to the next priority.
+
+Returns -1 when the user hangs up.
+
+</usage>
+    </application>
+    <application name="SetCIDNum">
+        <synopsis>Set CallerID Number</synopsis>
+        <usage>  SetCIDNum(cnum[|a]): Set Caller*ID Number on a call to a new
+value, while preserving the original Caller*ID name.  This is
+useful for providing additional information to the called
+party. Sets ANI as well if a flag is used.  Always returns 0
+
+</usage>
+    </application>
+    <application name="ResetCDR">
+        <synopsis>Resets the Call Data Record</synopsis>
+        <usage>  ResetCDR([options]):  Causes the Call Data Record to be reset, optionally
+storing the current CDR before zeroing it out (if &#39;w&#39; option is specifed).
+record WILL be stored.
+Always returns 0.
+
+</usage>
+    </application>
+    <application name="Playback">
+        <synopsis>Play a file</synopsis>
+        <usage>  Playback(filename[|option]):  Plays  back  a  given  filename (do not put
+extension). Options may also be  included following a pipe symbol. The &#39;skip&#39;
+option causes the playback of the message to  be  skipped  if  the  channel
+is not in the &#39;up&#39; state (i.e. it hasn&#39;t been  answered  yet. If &#39;skip&#39; is 
+specified, the application will return immediately should the channel not be
+off hook.  Otherwise, unless &#39;noanswer&#39; is specified, the channel channel will
+be answered before the sound is played. Not all channels support playing
+messages while still hook. Returns -1 if the channel was hung up, or if the
+file does not exist. Returns 0 otherwise.
+
+</usage>
+    </application>
+    <application name="Macro">
+        <synopsis>Macro Implementation</synopsis>
+        <usage>  Macro(macroname|arg1|arg2...): Executes a macro using the context
+&#39;macro-&lt;macroname&gt;&#39;, jumping to the &#39;s&#39; extension of that context and
+executing each step, then returning when the steps end.  The calling
+extension, context, and priority are stored in ${MACRO_EXTEN}, 
+${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become
+${ARG1}, ${ARG2}, etc in the macro context.  Macro returns -1 if
+any step in the macro returns -1, and 0 otherwise.  If you Goto out
+of the Macro context, the Macro will terminate and control will be return
+at the location of the Goto.  Otherwise if ${MACRO_OFFSET} is set at
+termination, Macro will attempt to continue at priority
+MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.
+
+</usage>
+    </application>
+    <application name="SIPDtmfMode">
+        <synopsis>Change the dtmfmode for a SIP call</synopsis>
+        <usage>SIPDtmfMode(inband|info|rfc2833): Changes the dtmfmode for a SIP call
+
+</usage>
+    </application>
+    <application name="WaitExten">
+        <synopsis>Waits for some time</synopsis>
+        <usage>  Wait(seconds): Waits for the user to enter a new extension for the 
+specified number of seconds, then returns 0.  Seconds can be passed with
+fractions of a second. (eg: 1.5 = 1.5 seconds)
+
+</usage>
+    </application>
+    <application name="Dial">
+        <synopsis>Place a call and connect to the current channel</synopsis>
+        <usage>  Dial(Technology/resource[&amp;Technology2/resource2...][|timeout][|options][|URL]):
+Requests one or more channels and places specified outgoing calls on them.
+As soon as a channel answers, the Dial app will answer the originating
+channel (if it needs to be answered) and will bridge a call with the channel
+which first answered. All other calls placed by the Dial app will be hung up
+If a timeout is not specified, the Dial application will wait indefinitely
+until either one of the called channels answers, the user hangs up, or all
+channels return busy or error. In general, the dialer will return 0 if it
+was unable to place the call, or the timeout expired. However, if all
+channels were busy, and there exists an extension with priority n+101 (where
+n is the priority of the dialer instance), then it will be the next
+executed extension (this allows you to setup different behavior on busy from
+no-answer).
+  This application returns -1 if the originating channel hangs up, or if the
+call is bridged and either of the parties in the bridge terminate the call.
+The option string may contain zero or more of the following characters:
+      &#39;t&#39; -- allow the called user transfer the calling user by hitting #.
+      &#39;T&#39; -- allow the calling user to transfer the call by hitting #.
+      &#39;f&#39; -- Forces callerid to be set as the extension of the line 
+             making/redirecting the outgoing call. For example, some PSTNs
+             don&#39;t allow callerids from other extensions then the ones
+             that are assigned to you.
+      &#39;r&#39; -- indicate ringing to the calling party, pass no audio until answered.
+      &#39;m&#39; -- provide hold music to the calling party until answered.
+      &#39;M(x) -- Executes the macro (x) upon connect of the call
+      &#39;h&#39; -- allow callee to hang up by hitting *.
+      &#39;H&#39; -- allow caller to hang up by hitting *.
+      &#39;C&#39; -- reset call detail record for this call.
+      &#39;P[(x)]&#39; -- privacy mode, using &#39;x&#39; as database if provided.
+      &#39;g&#39; -- goes on in context if the destination channel hangs up
+      &#39;e&#39; -- Force the caller to Explicitly accept the call
+      &#39;A(x)&#39; -- play an announcement to the called party, using x as file
+      &#39;S(x)&#39; -- hangup the call after x seconds AFTER called party picked up
+      &#39;D([digits])&#39;  -- Send DTMF digit string *after* called party has answered
+             but before the bridge. (w=500ms sec pause)
+      &#39;L(x[:y][:z])&#39; -- Limit the call to &#39;x&#39; ms warning when &#39;y&#39; ms are left
+             repeated every &#39;z&#39; ms) Only &#39;x&#39; is required, &#39;y&#39; and &#39;z&#39; are optional.
+             The following special variables are optional:
+             * LIMIT_PLAYAUDIO_CALLER    yes|no (default yes)
+                                         Play sounds to the caller.
+             * LIMIT_PLAYAUDIO_CALLEE    yes|no
+                                         Play sounds to the callee.
+             * LIMIT_TIMEOUT_FILE        File to play when time is up.
+             * LIMIT_CONNECT_FILE        File to play when call begins.
+             * LIMIT_WARNING_FILE        File to play as warning if &#39;y&#39; is defined.
+                        &#39;timeleft&#39; is a special sound macro to auto-say the time 
+                        left and is the default.
+
+  In addition to transferring the call, a call may be parked and then picked
+up by another user.
+  The optional URL will be sent to the called party if the channel supports it.
+  This application sets the following channel variables upon completion:
+      DIALEDTIME    Time from dial to answer
+      ANSWEREDTIME  Time for actual call
+      DIALSTATUS    The status of the call as a text string, one of
+             CHANUNAVAIL | CONGESTION | NOANSWER | BUSY | ANSWER | CANCEL
+
+</usage>
+    </application>
+    <application name="ParkedCall">
+        <synopsis>Answer a parked call</synopsis>
+        <usage>ParkedCall(exten):Used to connect to a parked call.  This Application is always
+registered internally and does not need to be explicitly added
+into the dialplan, although you should include the &#39;parkedcalls&#39;
+context.
+
+</usage>
+    </application>
+    <application name="ResponseTimeout">
+        <synopsis>Set maximum timeout awaiting response</synopsis>
+        <usage>  ResponseTimeout(seconds): Set the maximum amount of time permitted after
+falling through a series of priorities for a channel in which the user may
+begin typing an extension. If the user does not type an extension in this
+amount of time, control will pass to the &#39;t&#39; extension if it exists, and
+if not the call would be terminated. The default timeout is 10 seconds.
+Always returns 0.
+
+</usage>
+    </application>
+    <application name="Hangup">
+        <synopsis>Unconditional hangup</synopsis>
+        <usage>  Hangup(): Unconditionally hangs up a given channel by returning -1 always.
+
+</usage>
+    </application>
+    <application name="GotoIfTime">
+        <synopsis>Conditional goto on current time</synopsis>
+        <usage>  GotoIfTime(&lt;times&gt;|&lt;weekdays&gt;|&lt;mdays&gt;|&lt;months&gt;?[[context|]extension|]pri):
+If the current time matches the specified time, then branch to the specified
+extension. Each of the elements may be specified either as &#39;*&#39; (for always)
+or as a range. See the &#39;include&#39; syntax for details.
+</usage>
+    </application>
+    <application name="NoOp">
+        <synopsis>No operation</synopsis>
+        <usage>  NoOp(): No-operation; Does nothing.
+</usage>
+    </application>
+    <application name="SetCallerPres">
+        <synopsis>Set CallerID Presentation</synopsis>
+        <usage>  SetCallerPres(presentation): Set Caller*ID presentation on
+a call to a new value.  Sets ANI as well if a flag is used.
+Always returns 0.  Valid presentations are:
+
+      allowed_not_screened    : Presentation Allowed, Not Screened
+      allowed_passed_screen   : Presentation Allowed, Passed Screen
+      allowed_failed_screen   : Presentation Allowed, Failed Screen
+      allowed                 : Presentation Allowed, Network Number
+      prohib_not_screened     : Presentation Prohibited, Not Screened
+      prohib_passed_screen    : Presentation Prohibited, Passed Screen
+      prohib_failed_screen    : Presentation Prohibited, Failed Screen
+      prohib                  : Presentation Prohibited, Network Number
+      unavailable             : Number Unavailable
+
+
+</usage>
+    </application>
+    <application name="SetLanguage">
+        <synopsis>Sets user language</synopsis>
+        <usage>  SetLanguage(language):  Set  the  channel  language to &#39;language&#39;.  This
+information is used for the syntax in generation of numbers, and to choose
+a natural language file when available.
+  For example, if language is set to &#39;fr&#39; and the file &#39;demo-congrats&#39; is 
+requested  to  be  played,  if the file &#39;fr/demo-congrats&#39; exists, then
+it will play that file, and if not will play the normal &#39;demo-congrats&#39;.
+Always returns 0.
+
+</usage>
+    </application>
+    <application name="NoCDR">
+        <synopsis>Make sure asterisk doesn&#39;t save CDR for a certain call</synopsis>
+        <usage>NoCDR(): makes sure there won&#39;t be any CDR written for a certain call
+</usage>
+    </application>
+    <application name="StripMSD">
+        <synopsis>Strip leading digits</synopsis>
+        <usage>  StripMSD(count): Strips the leading &#39;count&#39; digits from the channel&#39;s
+associated extension. For example, the number 5551212 when stripped with a
+count of 3 would be changed to 1212. This app always returns 0, and the PBX
+will continue processing at the next priority for the *new* extension.
+  So, for example, if priority 3 of 5551212 is StripMSD 3, the next step
+executed will be priority 4 of 1212. If you switch into an extension which
+has no first step, the PBX will treat it as though the user dialed an
+invalid extension.
+
+</usage>
+    </application>
+    <application name="Progress">
+        <synopsis>Indicate progress</synopsis>
+        <usage>  Progress(): Request that the channel indicate in-band progress is 
+available to the user.
+Always returns 0.
+
+</usage>
+    </application>
+    <application name="ZapBarge">
+        <synopsis>Barge in (monitor) Zap channel</synopsis>
+        <usage>  ZapBarge([channel]): Barges in on a specified zap
+channel or prompts if one is not specified.  Returns
+-1 when caller user hangs up and is independent of the
+state of the channel being monitored.
+</usage>
+    </application>
+    <application name="GotoIf">
+        <synopsis>Conditional goto</synopsis>
+        <usage>  GotoIf(Condition?label1:label2): Go to label 1 if condition is
+true, to label2 if condition is false. Either label1 or label2 may be
+omitted (in that case, we just don&#39;t take the particular branch) but not
+both. Look for the condition syntax in examples or documentation.
+</usage>
+    </application>
+</asterisk>
\ No newline at end of file
diff --git a/shout/config/conf.xml b/shout/config/conf.xml
new file mode 100644 (file)
index 0000000..3175115
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<configuration>
+ <configtab name="storage" desc="Storage">
+  <configsection name="storage">
+   <configheader>Context Storage</configheader>
+   <configswitch name="driver" desc="What backend should we use for storing the list of valid contexts/customers?">Sql
+    <case name="Sql" desc="SQL">
+     <configsection name="params">
+      <configsql switchname="driverconfig" />
+      <configstring name="table" desc="Table to hold the list of contexts/customers" required="true">contexts</configstring>
+     </configsection>
+    </case>
+   </configswitch>
+  </configsection>
+ </configtab>
+ <configtab name="extensions" desc="Extensions">
+  <configsection name="extensions">
+   <configheader>Extension Storage</configheader>
+   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">Ldap
+    <case name="Ldap" desc="LDAP">
+     <configsection name="params">
+      <configldap switchname="driverconfig" />
+     </configsection>
+    </case>
+    <case name="Sql" desc="SQL">
+     <configsection name="params">
+      <configsql switchname="driverconfig" />
+      <configstring name="table" desc="Table to hold the list of extensions" required="true"></configstring>
+     </configsection>
+    </case>
+   </configswitch>
+  </configsection>
+ </configtab>
+ <configtab name="devices" desc="VoIP Devices">
+  <configsection name="devices">
+   <configheader>Device Storage</configheader>
+   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">Ldap
+    <case name="Ldap" desc="LDAP">
+     <configsection name="params">
+      <configldap switchname="driverconfig" />
+     </configsection>
+    </case>
+    <case name="Sql" desc="SQL">
+     <configsection name="params">
+      <configsql switchname="driverconfig" />
+      <configstring name="table" desc="Table to hold the device configuration data" required="true">sip_peers</configstring>
+     </configsection>
+    </case>
+   </configswitch>
+  </configsection>
+ </configtab>
+</configuration>
+
diff --git a/shout/devices.php b/shout/devices.php
new file mode 100644 (file)
index 0000000..cac6319
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Copyright 2009 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+@define('SHOUT_BASE', dirname(__FILE__));
+require_once SHOUT_BASE . '/lib/base.php';
+require_once SHOUT_BASE . '/lib/Forms/DeviceForm.php';
+
+$action = Horde_Util::getFormData('action');
+$devices = $shout_devices->getDevices($context);
+$devid = Horde_Util::getFormData('devid');
+$vars = Horde_Variables::getDefaultVariables();
+
+//$tabs = Shout::getTabs($context, $vars);
+
+$RENDERER = new Horde_Form_Renderer();
+
+$section = 'devices';
+$title = _("Devices: ");
+
+switch ($action) {
+    case 'save':
+        $Form = new DeviceDetailsForm($vars);
+
+        // Show the list if the save was successful, otherwise back to edit.
+        if ($Form->isSubmitted() && $Form->isValid()) {
+            try {
+                $shout_devices->saveDevice($Form->getVars());
+                $notification->push(_("Device settings saved."));
+            } catch (Exception $e) {
+                $notification->push($e);
+            }
+            $action = 'list';
+            break;
+        } else {
+            $action = 'edit';
+        }
+    case 'add':
+    case 'edit':
+        if ($action == 'add') {
+            $title .= _("New Device");
+            // Treat adds just like an empty edit
+            $action = 'edit';
+        } else {
+            $title .= sprintf(_("Edit Device %s"), $extension);
+
+        }
+
+        $FormName = 'DeviceDetailsForm';
+        $vars = new Horde_Variables($devices[$devid]);
+        $Form = new DeviceDetailsForm($vars);
+
+        $Form->open($RENDERER, $vars, Horde::applicationUrl('devices.php'), 'post');
+
+        break;
+
+
+    case 'delete':
+        $notification->push("Not supported.");
+        break;
+
+    case 'list':
+    default:
+        $action = 'list';
+        $title .= _("List Users");
+}
+
+require SHOUT_TEMPLATES . '/common-header.inc';
+require SHOUT_TEMPLATES . '/menu.inc';
+
+$notification->notify();
+
+//echo $tabs->render($section);
+
+require SHOUT_TEMPLATES . '/devices/' . $action . '.inc';
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/shout/dialplan.php b/shout/dialplan.php
new file mode 100644 (file)
index 0000000..883d2c1
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package shout
+ */
+
+if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
+    header('Location: /');
+    exit();
+}
+
+require_once SHOUT_BASE . '/lib/Dialplan.php';
+$dialplan = &$shout->getDialplan($context);
+
+// Set up the tree.
+$dpgui = Shout_Dialplan::singleton('x', $dialplan);
+
+//$action = Horde_Util::getFormData("action");
+// $action = 'manager';
+
+$title = _("Dialplan Manager");
+
+require SHOUT_TEMPLATES . '/common-header.inc';
+require SHOUT_TEMPLATES . '/menu.inc';
+
+$notification->notify();
+
+echo $tabs->render($section);
+
+// require SHOUT_BASE . "/dialplan/$action.php";
+
+require SHOUT_TEMPLATES . '/dialplan/manager.inc';
+
+// Horde::addScriptFile('httpclient.js', 'horde', true);
+// Horde::addScriptFile('hideable.js', 'horde', true);
+// require HORDE_TEMPLATES . '/common-header.inc';
+// require HORDE_TEMPLATES . '/portal/sidebar.inc';
+
+
+// require SHOUT_TEMPLATES . "/dialplan/dialplanlist.inc";
+
+
+
+
+
+
+
+
+
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/shout/dialplan/edit.php b/shout/dialplan/edit.php
new file mode 100644 (file)
index 0000000..18ef2b4
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file LICENSE for license information (GPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/gpl.php.
+ */
+@define('SHOUT_BASE', dirname(__FILE__) . '/..');
+require_once SHOUT_BASE . '/lib/Dialplan.php';
+require_once 'Horde/Variables.php';
+
+$RENDERER = &new Horde_Form_Renderer();
+
+$empty = '';
+
+$vars = &Variables::getDefaultVariables($empty);
+$formname = $vars->get('formname');
+$context = $vars->get('context');
+$extension = $vars->get('extension');
+$dialplan = &$shout->getDialplan($context);
+
+$ExtensionDetailsForm = &Horde_Form::singleton('ExtensionDetailsForm', $vars);
+$ExtensionDetailsFormValid = $ExtensionDetailsForm->validate($vars, true);
+
+$ExtensionDetailsForm->open($RENDERER, $vars, 'dialplan.php', 'post');
+$ExtensionDetailsForm->preserveVarByPost($vars, "section");
+$ExtensionDetailsForm->preserve($vars);
+require SHOUT_TEMPLATES . '/table-limiter-begin.inc';
+$RENDERER->beginActive($ExtensionDetailsForm->getTitle());
+$RENDERER->renderFormActive($ExtensionDetailsForm, $vars);
+# FIXME Maybe this should be a subclass inheriting from the From/Renderer object
+# instead of a simple include?
+$i = 0;
+require SHOUT_TEMPLATES . '/dialplan/priority-form-begin.inc';
+foreach ($dialplan['extensions'][$extension] as $priority => $application) {
+    require SHOUT_TEMPLATES . '/dialplan/priority-form-line.inc';
+    $i++;
+}
+require SHOUT_TEMPLATES . '/dialplan/priority-form-end.inc';
+$RENDERER->submit('Add Priority');
+$RENDERER->submit('Add 5 Priorities');
+$RENDERER->submit('Save');
+$RENDERER->end();
+$ExtensionDetailsForm->close($RENDERER);
+require SHOUT_TEMPLATES . '/table-limiter-end.inc';
\ No newline at end of file
diff --git a/shout/extensions.php b/shout/extensions.php
new file mode 100644 (file)
index 0000000..295d165
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+@define('SHOUT_BASE', dirname(__FILE__));
+require_once SHOUT_BASE . '/lib/base.php';
+require_once SHOUT_BASE . '/lib/Forms/ExtensionForm.php';
+//require_once SHOUT_BASE . '/lib/Shout.php';
+
+$action = Horde_Util::getFormData('action');
+$extension = Horde_Util::getFormData('extension');
+$extensions = $shout_extensions->getExtensions($context);
+
+$vars = Horde_Variables::getDefaultVariables();
+
+//$tabs = Shout::getTabs($context, $vars);
+
+$RENDERER = new Horde_Form_Renderer();
+
+$section = 'extensions';
+$title = _("Extensions: ");
+
+switch ($action) {
+case 'save':
+    $title .= sprintf(_("Save Extension %s"), $extension);
+    $FormName = $vars->get('formname');
+
+    $Form = &Horde_Form::singleton($FormName, $vars);
+
+    $FormValid = $Form->validate($vars, true);
+
+    if ($Form->isSubmitted() && $FormValid) {
+        // Form is Valid and Submitted
+        try {
+            $Form->execute();
+        } catch (Exception $e) {
+            $notification->push($e);
+        }
+        $notification->push(_("User information updated."),
+                                  'horde.success');
+        break;
+     } else {
+         $action = 'edit';
+         // Fall-through to the "edit" action
+     }
+
+case 'add':
+case 'edit':
+    if ($action == 'add') {
+        $title .= _("New Extension");
+        // Treat adds just like an empty edit
+        $action = 'edit';
+    } else {
+        $title .= sprintf(_("Edit Extension %s"), $extension);
+
+    }
+
+    $FormName = 'ExtensionDetailsForm';
+    $vars = new Horde_Variables($extensions[$extension]);
+    $Form = &Horde_Form::singleton($FormName, $vars);
+
+    $Form->open($RENDERER, $vars, Horde::applicationUrl('extensions.php'), 'post');
+
+    break;
+
+case 'delete':
+    $title .= sprintf(_("Delete Extension %s"), $extension);
+    $extension = Horde_Util::getFormData('extension');
+
+    $res = $shout->deleteUser($context, $extension);
+
+    if (!$res) {
+        echo "Failed!";
+        print_r($res);
+    }
+    $notification->push("User Deleted.");
+    break;
+
+case 'list':
+default:
+    $action = 'list';
+    $title .= _("List Users");
+}
+
+require SHOUT_TEMPLATES . '/common-header.inc';
+require SHOUT_TEMPLATES . '/menu.inc';
+
+$notification->notify();
+
+//echo $tabs->render($section);
+
+require SHOUT_TEMPLATES . '/extensions/' . $action . '.inc';
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/shout/index.php b/shout/index.php
new file mode 100644 (file)
index 0000000..4097661
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package shout
+ */
+
+@define('SHOUT_BASE', dirname(__FILE__));
+$shout_configured = (is_readable(SHOUT_BASE . '/config/conf.php'));
+
+if (!$shout_configured) {
+require SHOUT_BASE . '/../lib/Test.php';
+    Horde_Test::configFilesMissing('Shout', SHOUT_BASE,
+        array('conf.php'));
+}
+
+require_once SHOUT_BASE . '/lib/base.php';
+header('Location: ' . Horde::applicationUrl('extensions.php'));
diff --git a/shout/lib/Dialplan.php b/shout/lib/Dialplan.php
new file mode 100644 (file)
index 0000000..0549b32
--- /dev/null
@@ -0,0 +1,227 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file LICENSE for license information (GPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/gpl.php.
+ *
+ * @package Shout
+ */
+// {{{
+/**
+ * The Shout_Dialplan:: class provides an interactive view of an Asterisk dialplan.
+ * It allows for expanding/collapsing of extensions and priorities and maintains their state.
+ * It can work together with the Horde_Tree javascript class to achieve this in
+ * DHTML on supported browsers.
+ *
+ * Copyright 2005 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * $Id$
+ *
+ * @author  Ben Klang <ben@alkaloid.net>
+ * @package Shout_Dialplan
+ * @since   Shout 0.1
+ */
+class Shout_Dialplan
+{
+    /**
+     * The name of this instance.
+     *
+     * @var string
+     */
+    var $_instance = null;
+
+    /**
+     * The array of dialplan information to render the form
+     *
+     * @var array
+     */
+    var $_dialplan = array();
+
+    /**
+     * Object containing the instantiation of the Horde_Tree class
+     *
+     * @var object
+     */
+     var $_tree = null;
+
+    /**
+     * Create or return a unique instance of the Shout_Dialplan object
+     *
+     * @param string $instance Unique identifier for this instance
+     * @param array $dialplan Dialplan array as returned by the driver
+     * @return object Instantiation of the Shout_Dialplan object
+     */
+     function &singleton($instance, $dialplan)
+     {
+        static $instances = array();
+
+        if (isset($instances[$instance])) {
+            return $instances[$instance];
+        }
+        $instances[$instance] = new Shout_Dialplan($instance, $dialplan);
+        return $instances[$instance];
+    }
+
+    /**
+     * Instantiator for the Shout_Dialplan
+     *
+     * @param string $instance Unique identifier for this instance
+     * @param array $dialplan Dialplan array as returned by the driver
+     * @return Shout_Dialplan Instantiation of the Shout_Dialplan object
+     */
+    function Shout_Dialplan($instance, $dialplan)
+    {
+        require_once 'Horde/Tree.php';
+        require_once 'Horde/Block.php';
+        require_once 'Horde/Block/Collection.php';
+
+        $this->_instance = $instance;
+        $this->_dialplan = $dialplan;
+        $this->_tree = Horde_Tree::singleton('shout_dialplan_nav_'.$instance, 'javascript');
+
+        foreach ($this->_dialplan as $linetype => $linedata) {
+            switch($linetype) {
+                case 'extensions':
+                    $url = '#top';
+                    $this->_tree->addNode('extensions', null, 'Extensions', null, array('url' => $url));
+                    foreach ($linedata as $extension => $priorities) {
+                        $nodetext = Shout::exten2name($extension);
+                        $url = Horde::applicationUrl('index.php?section=dialplan' .
+                            '&extension=' . $extension . '&context=' . $this->_dialplan['name']);
+                        $url = "#$extension";
+                        $this->_tree->addNode("extension_".$extension, 'extensions', $nodetext,
+                            null, false,
+                            array(
+                                'url' => $url,
+                                'onclick' =>
+                                    'shout_dialplan_object_'.$this->_instance.
+                                        '.highlightExten(\''.$extension.'\')',
+                            )
+                        );
+        //                 foreach ($priorities as $priority => $application) {
+        //                     $this->_tree->addNode("$extension-$priority", $extension, "$priority: $application", null);
+        //                 }
+                    }
+                    break;
+
+                case 'includes':
+                    $this->_tree->addNode('includes', null, 'Includes', null);
+                    foreach ($linedata as $include) {
+                        $url = Horde::applicationUrl('index.php?section=dialplan&context='.$include);
+                        $this->_tree->addNode("include_$include", 'includes', $include, null,
+                            true, array('url' => $url));
+                    }
+                    break;
+
+                # TODO Ignoring ignorepat lines for now
+
+                case 'barelines':
+                    $this->_tree->addNode('barelines', null, 'Extra Settings', null);
+                    $i = 0;
+                    foreach ($linedata as $bareline) {
+                        $this->_tree->addNode("bareline_".$i, 'barelines', $bareline, null);
+                        $i++;
+                    }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Render dialplan side navigation tree
+     */
+    function renderNavTree()
+    {
+        print '<div id=\'contextTree\'>'."\n";
+        $this->_tree->renderTree(true);
+        print '    <br />'."\n";
+        print '    <a href="#top" class="small">Back to Top</a>'."\n";
+        print '</div>'."\n";
+        return true;
+    }
+
+    function generateAppList()
+    {
+        $applist = Shout::getApplist();
+        print '<script language="JavaScript" type="text/javascript">'."\n";
+        print '<!--'."\n";
+        print 'var shout_dialplan_applist_'.$this->_instance.' = new Array();'."\n";
+
+        $i = 0;
+        foreach ($applist as $app => $appdata) {
+            print 'shout_dialplan_applist_'.$this->_instance.'['.$i.'] = \''.$app.'\''."\n";
+            $i++;
+        }
+        print '//-->'."\n";
+        print '</script>'."\n";
+        return true;
+    }
+
+    function renderExtensions()
+    {
+        if(!isset($this->_dialplan['extensions'])) {
+            print '<div id="extensionDetail">'."\n";
+            print '    <div class="extensionBox">No Configured Extensions</div>'."\n";
+            print '</div>'."\n";
+        } else {
+            print '<script language="JavaScript" type="text/javascript"';
+            print ' src="/services/javascript.php?file=dialplan.js&amp;app=shout"></script>'."\n";
+            print '<script language="JavaScript" type="text/javascript">'."\n";
+            print '<!--'."\n";
+            print 'var shout_dialplan_entry_'.$this->_instance.' = new Array();'."\n";
+            foreach($this->_dialplan['extensions'] as $extension => $priorities) {
+                print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'] = new Array();'."\n";
+                print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'][\'name\'] =';
+                print '\''.Shout::exten2name($extension).'\';'."\n";
+                print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'][\'priorities\']';
+                print ' = new Array();'."\n";
+                foreach($priorities as $priority => $data) {
+                    print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\']';
+                    print '[\'priorities\']['.$priority.'] = new Array();'."\n";
+                    print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\']';
+                    print '[\'priorities\']['.$priority.'][\'application\'] = ';
+                    print '\''.$data['application'].'\';'."\n";
+                    print 'shout_dialplan_entry_'.$this->_instance.'[\''.$extension.'\'][\'priorities\']';
+                    print '['.$priority.'][\'args\'] = \''.$data['args'].'\';'."\n";
+                }
+            }
+            print 'var shout_dialplan_object_'.$this->_instance.' = new Dialplan(\''.$this->_instance.'\');'."\n";
+            print '//-->'."\n";
+            print '</script>'."\n";
+
+            print '<form id="shout_dialplan_form_'.$this->_instance.'" action="#">'."\n";
+            print '<div id="extensionDetail">'."\n";
+            $e = 0;
+            foreach($this->_dialplan['extensions'] as $extension => $priorities) {
+                print '<div class="extension" ';
+                    print 'id="extension_'.$extension.'">';
+                    print '<div class="extensionBox" ';
+                        print 'id="eBox-'.$extension.'" ';
+                        print 'onclick="javascript:shout_dialplan_object_'.$this->_instance.'.highlightExten';
+                            print '(\''.$extension.'\');">'."\n";
+                        print '<a name="'.$extension.'">'."\n";
+                            print Shout::exten2name($extension);
+                        print '</a>'."\n";
+                    print '</div>'."\n";
+                    print '<div id="pList-'.$extension.'">'."\n";
+                    print '</div>'."\n";
+                $e++;
+                print '</div>'."\n";
+                print '<br />'."\n";
+                print '<script language="JavaScript" type="text/javascript">'."\n";
+                print '<!--'."\n";
+                print 'shout_dialplan_object_'.$this->_instance.'.drawPrioTable(\''.$extension.'\');'."\n";
+                print '//-->'."\n";
+                print '</script>'."\n";
+            }
+            print '</div>'."\n";
+            print '</form>'."\n";
+        }
+    }
+}
\ No newline at end of file
diff --git a/shout/lib/Driver.php b/shout/lib/Driver.php
new file mode 100644 (file)
index 0000000..421afd3
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Shout_Driver:: defines an API for implementing storage backends for Shout.
+ *
+ * $Id$
+ *
+ * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Ben Klang <ben@alkaloid.net>
+ * @version $Revision: 76 $
+ * @since   Shout 0.1
+ * @package Shout
+ */
+
+class Shout_Driver {
+
+    /**
+     * Hash containing connection parameters.
+     *
+     * @var array $_params
+     */
+    var $_params = array();
+
+    function Shout_Driver($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /**
+    * Get a list of contexts from the instantiated driver and filter
+    * the returned contexts for those which the current user can see/edit
+    *
+    * @param optional string $filter Filter for types of contexts to return.
+    *                                One of "system" "customer" or "all"
+    *
+    * @param optional string $filterperms Filter contexts for given permissions
+    *
+    * @return array Contexts valid for this user
+    *
+    * @access public
+    */
+    function getContexts($filters = "all", $filterperms = null)
+    {
+        throw new Shout_Exception("This function is not implemented.");
+    }
+
+    /**
+     * For the given context and type, make sure the context has the
+     * appropriate properties, that it is effectively of that "type"
+     *
+     * @param string $context the context to check type for
+     *
+     * @param string $type the type to verify the context is of
+     *
+     * @return boolean true of the context is of type, false if not
+     *
+     * @access public
+     */
+    function checkContextType($context, $type)
+    {
+        throw new Shout_Exception("This function is not implemented.");
+    }
+
+    /**
+     * Get a list of users valid for the current context.  Return an array
+     * indexed by the extension.
+     *
+     * @param string $context Context for which users should be returned
+     *
+     * @return array User information indexed by voice mailbox number
+     */
+    function getUsers($context)
+    {
+        throw new Shout_Exception("This function is not implemented.");
+    }
+
+    /**
+     * Returns the name of the user's default context
+     *
+     * @return string User's default context
+     */
+    function getHomeContext()
+    {
+        throw new Shout_Exception("This function is not implemented.");
+    }
+
+    /**
+     * Get a context's properties
+     *
+     * @param string $context Context to get properties for
+     *
+     * @return integer Bitfield of properties valid for this context
+     */
+    function getContextProperties($context)
+    {
+        throw new Shout_Exception("This function is not implemented.");
+    }
+
+    /**
+     * Get a context's extensions and return as a multi-dimensional associative
+     * array
+     *
+     * @param string $context Context to return extensions for
+     *
+     * @return array Multi-dimensional associative array of extensions data
+     *
+     */
+    function getDialplan($context)
+    {
+        throw new Shout_Exception("This function is not implemented.");
+    }
+
+    /**
+     * Attempts to return a concrete Shout_Driver instance based on
+     * $driver.
+     *
+     * @param string $driver  The type of the concrete Shout_Driver subclass
+     *                        to return.  The class name is based on the storage
+     *                        driver ($driver).  The code is dynamically
+     *                        included.
+     *
+     * @param array  $params  (optional) A hash containing any additional
+     *                        configuration or connection parameters a
+     *                        subclass might need.
+     *
+     * @return mixed  The newly created concrete Shout_Driver instance, or
+     *                false on an error.
+     */
+    function &factory($class, $driver = null, $params = null)
+    {
+        if (is_null($driver)) {
+            $driver = $GLOBALS['conf'][$class]['driver'];
+        }
+
+        $driver = basename($driver);
+
+        if (is_null($params)) {
+            if ($GLOBALS['conf'][$class]['params']['driverconfig'] == 'horde') {
+                $params = array_merge(Horde::getDriverConfig('storage', $driver),
+                                      $GLOBALS['conf'][$class]['params']);
+            } else {
+                $params = $GLOBALS['conf'][$class]['params'];
+            }
+        }
+
+        $params['class'] = $class;
+
+        require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
+        $class = 'Shout_Driver_' . $driver;
+        if (class_exists($class)) {
+            return new $class($params);
+        } else {
+            return false;
+        }
+    }
+
+}
diff --git a/shout/lib/Driver/Ldap.php b/shout/lib/Driver/Ldap.php
new file mode 100644 (file)
index 0000000..ee0358f
--- /dev/null
@@ -0,0 +1,776 @@
+<?php
+
+class Shout_Driver_Ldap extends Shout_Driver
+{
+    var $_ldapKey;  // Index used for storing objects
+    var $_appKey;   // Index used for moving info to/from the app
+
+    /**
+     * Handle for the current database connection.
+     * @var object LDAP $_LDAP
+     */
+    private $_LDAP;
+
+    /**
+     * Boolean indicating whether or not we're connected to the LDAP
+     * server.
+     * @var boolean $_connected
+     */
+    private $_connected = false;
+
+
+    /**
+    * Constructs a new Shout LDAP driver object.
+    *
+    * @param array  $params    A hash containing connection parameters.
+    */
+    function __construct($params = array())
+    {
+        parent::__construct($params);
+        $this->_connect();
+
+        /* These next lines will translate between indexes used in the
+         * application and LDAP.  The rationale is that translation here will
+         * help make Congregation more driver-independant.  The keys used to
+         * contruct user arrays should be more appropriate to human-legibility
+         * (name instead of 'cn' and email instead of 'mail').  This translation
+         * is only needed because LDAP indexes users based on an arbitrary
+         * attribute and the application indexes by extension/context.  In my
+         * environment users are indexed by their 'mail' attribute and others
+         * may index based on 'cn' or 'uid'.  Any time a new $prefs['uid'] needs
+         * to be supported, this function should be checked and possibly
+         * extended to handle that translation.
+         */
+        switch($this->_params['uid']) {
+        case 'cn':
+            $this->_ldapKey = 'cn';
+            $this->_appKey = 'name';
+            break;
+        case 'mail':
+            $this->_ldapKey = 'mail';
+            $this->_appKey = 'email';
+            break;
+        case 'uid':
+            # FIXME Probably a better app key to map here
+            # There is no value that maps uid to LDAP so we can choose to use
+            # either extension or name, or anything really.  I want to
+            # support it since it's a very common DN attribute.
+            # Since it's entirely administrator's preference, I'll
+            # set it to name for now
+            $this->_ldapKey = 'uid';
+            $this->_appKey = 'name';
+            break;
+        case 'voiceMailbox':
+            $this->_ldapKey = 'voiceMailbox';
+            $this->_appKey = 'extension';
+            break;
+        }
+    }
+
+    /**
+     * Get a list of users valid for the contexts
+     *
+     * @param string $context  Context in which to search
+     *
+     * @return array User information indexed by voice mailbox number
+     */
+    public function getExtensions($context)
+    {
+
+        static $entries = array();
+        if (isset($entries[$context])) {
+            return $entries[$context];
+        }
+
+        $this->_params['basedn'];
+
+        $filter  = '(&';
+        $filter .= '(objectClass=AsteriskVoiceMail)';
+        $filter .= '(AstContext='.$context.')';
+        $filter .= ')';
+
+        $attributes = array(
+            'cn',
+            'mail',
+            'AstVoicemailMailbox',
+            'AstVoicemailPassword',
+            'AstVoicemailOptions',
+            'AstVoicemailPager',
+            'telephoneNumber',
+            'AstExtension'
+        );
+
+        $search = ldap_search($this->_LDAP, $this->_params['basedn'], $filter, $attributes);
+        if ($search === false) {
+            throw new Shout_Exception("Unable to search directory: " .
+                ldap_error($this->_LDAP), ldap_errno($this->_LDAP));
+        }
+
+        $res = ldap_get_entries($this->_LDAP, $search);
+        if ($res === false) {
+            throw new Shout_Exception("Unable to fetch results from directory: " .
+                ldap_error($this->_LDAP), ldap_errno($this->_LDAP));
+        }
+
+        // ATTRIBUTES RETURNED FROM ldap_get_entries ARE ALL LOWER CASE!!
+        // It's a PHP thing.
+        $entries[$context] = array();
+        $i = 0;
+        while ($i < $res['count']) {
+            list($extension) = explode('@', $res[$i]['astvoicemailmailbox'][0]);
+            $entries[$context][$extension] = array('extension' => $extension);
+
+            $j = 0;
+            $entries[$context][$extension]['mailboxopts'] = array();
+            if (empty($res[$i]['astvoicemailoptions']['count'])) {
+                $res[$i]['astvoicemailoptions']['count'] = -1;
+            }
+            while ($j < $res[$i]['astvoicemailoptions']['count']) {
+                $entries[$context][$extension]['mailboxopts'][] =
+                    $res[$i]['astvoicemailoptions'][$j];
+                $j++;
+            }
+
+            $entries[$context][$extension]['mailboxpin'] =
+                $res[$i]['astvoicemailpassword'][0];
+
+            $entries[$context][$extension]['name'] =
+                $res[$i]['cn'][0];
+
+            $entries[$context][$extension]['email'] =
+                $res[$i]['mail'][0];
+
+            $entries[$context][$extension]['pageremail'] =
+                $res[$i]['astvoicemailpager'][0];
+
+            $j = 0;
+            $entries[$context][$extension]['numbers'] = array();
+            if (empty($res[$i]['telephonenumber']['count'])) {
+                $res[$i]['telephonenumber']['count'] = -1;
+            }
+            while ($j < $res[$i]['telephonenumber']['count']) {
+                $entries[$context][$extension]['numbers'][] =
+                    $res[$i]['telephonenumber'][$j];
+                $j++;
+            }
+
+            $j = 0;
+            $entries[$context][$extension]['devices'] = array();
+            if (empty($res[$i]['astextension']['count'])) {
+                $res[$i]['astextension']['count'] = -1;
+            }
+            while ($j < $res[$i]['astextension']['count']) {
+                $entries[$context][$extension]['devices'][] =
+                    $res[$i]['astextension'][$j];
+                $j++;
+            }
+
+
+            $i++;
+
+        }
+
+        ksort($entries[$context]);
+
+        return($entries[$context]);
+    }
+
+    /**
+     * Get a list of destinations valid for this extension.
+     * A destination is either a telephone number, a VoIP device or an
+     * Instant Messaging address (a special case of VoIP).
+     *
+     * @param string $context    Context for the extension
+     * @param string $extension  Extension for which to return destinations
+     */
+    function getDestinations($context, $extension)
+    {
+        // FIXME: LDAP filter injection
+        $filter = '(&(AstContext=%s)(AstVoicemailMailbox=%s))';
+        $filter = sprintf($filter, $context, $extension);
+
+        $attrs = array('telephoneNumber', 'AstExtensions');
+
+        $res = ldap_search($this->_LDAP, $this->_params['basedn'],
+                           $filter, $attrs);
+
+        if ($res === false) {
+            $msg = sprintf('Error while searching LDAP.  Code %s; Message "%s"',
+                           ldap_errno($this->_LDAP), ldap_error($this->_LDAP));
+            Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Shout_Exception(_("Internal error searching the directory."));
+        }
+
+        $res = ldap_get_entries($this->_LDAP, $res);
+
+        if ($res === false) {
+            $msg = sprintf('Error while searching LDAP.  Code %s; Message "%s"',
+                           ldap_errno($this->_LDAP), ldap_error($this->_LDAP));
+            Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Shout_Exception(_("Internal error searching the directory."));
+        }
+
+        if ($res['count'] != 1) {
+            $msg = sprintf('Error while searching LDAP.  Code %s; Message "%s"',
+                           ldap_errno($this->_LDAP), ldap_error($this->_LDAP));
+            Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Shout_Exception(_("Wrong number of entries found for this search."));
+        }
+
+        return array('numbers' => $res['telephonenumbers'],
+                     'devices' => $res['astextensions']);
+    }
+
+    /**
+     * Get a context's dialplan and return as a multi-dimensional associative
+     * array
+     *
+     * @param string $context Context to return extensions for
+     *
+     * @param boolean $preprocess Parse includes and barelines and add their
+     *                            information into the extensions array
+     *
+     * @return array Multi-dimensional associative array of extensions data
+     *
+     */
+    public function getDialplan($context, $preprocess = false)
+    {
+        # FIXME Implement preprocess functionality.  Don't forget to cache!
+        static $dialplans = array();
+        if (isset($dialplans[$context])) {
+            return $dialplans[$context];
+        }
+
+        $res = @ldap_search($this->_LDAP,
+            SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
+            "(&(objectClass=".SHOUT_CONTEXT_EXTENSIONS_OBJECTCLASS.")(context=$context))",
+            array(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE, SHOUT_DIALPLAN_INCLUDE_ATTRIBUTE,
+                SHOUT_DIALPLAN_IGNOREPAT_ATTRIBUTE, 'description',
+                SHOUT_DIALPLAN_BARELINE_ATTRIBUTE));
+        if (!$res) {
+            return PEAR::raiseError("Unable to locate any extensions " .
+            "underneath ".SHOUT_ASTERISK_BRANCH.",".$this->_params['basedn'] .
+            " matching those search filters");
+        }
+
+        $res = ldap_get_entries($this->_LDAP, $res);
+        $dialplans[$context] = array();
+        $dialplans[$context]['name'] = $context;
+        $i = 0;
+        while ($i < $res['count']) {
+            # Handle extension lines
+            if (isset($res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)])) {
+                $j = 0;
+                while ($j < $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)]['count']) {
+                    @$line = $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)][$j];
+
+                    # Basic sanity check for length.  FIXME
+                    if (strlen($line) < 5) {
+                        break;
+                    }
+                    # Can't use strtok here because there may be commass in the
+                    # arg string
+
+                    # Get the extension
+                    $token1 = strpos($line, ',');
+                    $token2 = strpos($line, ',', $token1 + 1);
+                    $token3 = strpos($line, '(', $token2 + 1);
+
+                    $extension = substr($line, 0, $token1);
+                    if (!isset($dialplans[$context]['extensions'][$extension])) {
+                        $dialplan[$context]['extensions'][$extension] = array();
+                    }
+                    $token1++;
+                    # Get the priority
+                    $priority = substr($line, $token1, $token2 - $token1);
+                    $dialplans[$context]['extensions'][$extension][$priority] =
+                        array();
+                    $token2++;
+
+                    # Get Application and args
+                    $application = substr($line, $token2, $token3 - $token2);
+
+                    if ($token3) {
+                        $application = substr($line, $token2, $token3 - $token2);
+                        $args = substr($line, $token3);
+                        $args = preg_replace('/^\(/', '', $args);
+                        $args = preg_replace('/\)$/', '', $args);
+                    } else {
+                        # This application must not have any args
+                        $application = substr($line, $token2);
+                        $args = '';
+                    }
+
+                    # Merge all that data into the returning array
+                    $dialplans[$context]['extensions'][$extension][$priority]['application'] =
+                        $application;
+                    $dialplans[$context]['extensions'][$extension][$priority]['args'] =
+                        $args;
+                    $j++;
+                }
+
+                # Sort the extensions data
+                foreach ($dialplans[$context]['extensions'] as
+                    $extension => $data) {
+                    ksort($dialplans[$context]['extensions'][$extension]);
+                }
+                uksort($dialplans[$context]['extensions'],
+                    array(new Shout, "extensort"));
+            }
+            # Handle include lines
+            if (isset($res[$i]['asteriskincludeline'])) {
+                $j = 0;
+                while ($j < $res[$i]['asteriskincludeline']['count']) {
+                    @$line = $res[$i]['asteriskincludeline'][$j];
+                    $dialplans[$context]['includes'][$j] = $line;
+                    $j++;
+                }
+            }
+
+            # Handle ignorepat
+            if (isset($res[$i]['asteriskignorepat'])) {
+                $j = 0;
+                while ($j < $res[$i]['asteriskignorepat']['count']) {
+                    @$line = $res[$i]['asteriskignorepat'][$j];
+                    $dialplans[$context]['ignorepats'][$j] = $line;
+                    $j++;
+                }
+            }
+            # Handle ignorepat
+            if (isset($res[$i]['asteriskextensionbareline'])) {
+                $j = 0;
+                while ($j < $res[$i]['asteriskextensionbareline']['count']) {
+                    @$line = $res[$i]['asteriskextensionbareline'][$j];
+                    $dialplans[$context]['barelines'][$j] = $line;
+                    $j++;
+                }
+            }
+
+            # Increment object
+            $i++;
+        }
+        return $dialplans[$context];
+    }
+
+    /**
+     * Get the limits for the current user, the user's context, and global
+     * Return the most specific values in every case.  Return default values
+     * where no data is found.  If $extension is specified, $context must
+     * also be specified.
+     *
+     * @param optional string $context Context to search
+     *
+     * @param optional string $extension Extension/user to search
+     *
+     * @return array Array with elements indicating various limits
+     */
+     # FIXME Figure out how this fits into Shout/Congregation better
+    public function getLimits($context = null, $extension = null)
+    {
+
+        $limits = array('telephonenumbersmax',
+                        'voicemailboxesmax',
+                        'asteriskusersmax');
+
+        if(!is_null($extension) && is_null($context)) {
+            return PEAR::raiseError("Extension specified but no context " .
+                "given.");
+        }
+
+        if (!is_null($context) && isset($limits[$context])) {
+            if (!is_null($extension) &&
+                isset($limits[$context][$extension])) {
+                return $limits[$context][$extension];
+            }
+            return $limits[$context];
+        }
+
+        # Set some default limits (to unlimited)
+        static $cachedlimits = array();
+        # Initialize the limits with defaults
+        if (count($cachedlimits) < 1) {
+            foreach ($limits as $limit) {
+                $cachedlimits[$limit] = 99999;
+            }
+        }
+
+        # Collect the global limits
+        $res = @ldap_search($this->_LDAP,
+            SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
+            '(&(objectClass=asteriskLimits)(cn=globals))',
+            $limits);
+
+        if (!$res) {
+            return PEAR::raiseError('Unable to search the LDAP server for ' .
+                'global limits');
+        }
+
+        $res = ldap_get_entries($this->_LDAP, $res);
+        # There should only have been one object returned so we'll just take the
+        # first result returned
+        if ($res['count'] > 0) {
+            foreach ($limits as $limit) {
+                if (isset($res[0][$limit][0])) {
+                    $cachedlimits[$limit] = $res[0][$limit][0];
+                }
+            }
+        } else {
+            return PEAR::raiseError("No global object found.");
+        }
+
+        # Get limits for the context, if provided
+        if (isset($context)) {
+            $res = ldap_search($this->_LDAP,
+                SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
+                "(&(objectClass=asteriskLimits)(cn=$context))");
+
+            if (!$res) {
+                return PEAR::raiseError('Unable to search the LDAP server ' .
+                    "for $context specific limits");
+            }
+
+            $cachedlimits[$context][$extension] = array();
+            if ($res['count'] > 0) {
+                foreach ($limits as $limit) {
+                    if (isset($res[0][$limit][0])) {
+                        $cachedlimits[$context][$limit] = $res[0][$limit][0];
+                    } else {
+                        # If no value is provided use the global limit
+                        $cachedlimits[$context][$limit] = $cachedlimits[$limit];
+                    }
+                }
+            } else {
+
+                foreach ($limits as $limit) {
+                    $cachedlimits[$context][$limit] =
+                        $cachedlimits[$limit];
+                }
+            }
+
+            if (isset($extension)) {
+                $res = @ldap_search($this->_LDAP,
+                    SHOUT_USERS_BRANCH.','.$this->_params['basedn'],
+                    "(&(objectClass=asteriskLimits)(voiceMailbox=$extension)".
+                    "(context=$context))");
+
+                if (!$res) {
+                    return PEAR::raiseError('Unable to search the LDAP server '.
+                        "for Extension $extension, $context specific limits");
+                }
+
+                $cachedlimits[$context][$extension] = array();
+                if ($res['count'] > 0) {
+                    foreach ($limits as $limit) {
+                        if (isset($res[0][$limit][0])) {
+                            $cachedlimits[$context][$extension][$limit] =
+                                $res[0][$limit][0];
+                        } else {
+                            # If no value is provided use the context limit
+                            $cachedlimits[$context][$extension][$limit] =
+                                $cachedlimits[$context][$limit];
+                        }
+                    }
+                } else {
+                    foreach ($limits as $limit) {
+                        $cachedlimits[$context][$extension][$limit] =
+                            $cachedlimits[$context][$limit];
+                    }
+                }
+                return $cachedlimits[$context][$extension];
+            }
+            return $cachedlimits[$context];
+        }
+    }
+
+    /**
+     * Save an extension to the LDAP tree
+     *
+     * @param string $context Context to which the user should be added
+     *
+     * @param string $extension Extension to be saved
+     *
+     * @param array $details Phone numbers, PIN, options, etc to be saved
+     *
+     * @return TRUE on success, PEAR::Error object on error
+     */
+    public function saveExtension($context, $extension, $details)
+    {
+        $ldapKey = &$this->_ldapKey;
+        $appKey = &$this->_appKey;
+        # FIXME Access Control/Authorization
+        if (!Shout::checkRights("shout:contexts:$context:extensions", PERMS_EDIT, 1)) {
+            // FIXME: Allow users to edit themselves
+            //&& !($details[$appKey] == Auth::getAuth())) {
+            throw new Shout_Exception(_("Permission denied to save extensions in this context."));
+        }
+
+        $contexts = &$this->getContexts();
+//         $domain = $contexts[$context]['domain'];
+
+        # Check to ensure the extension is unique within this context
+        $filter = "(&(objectClass=AstVoicemailMailbox)(context=$context))";
+        $reqattrs = array('dn', $ldapKey);
+        $res = @ldap_search($this->_LDAP,
+            SHOUT_USERS_BRANCH . ',' . $this->_params['basedn'],
+            $filter, $reqattrs);
+        if (!$res) {
+            return PEAR::raiseError('Unable to check directory for duplicate extension: ' .
+                ldap_error($this->_LDAP));
+        }
+        if (($res['count'] > 1) ||
+            ($res['count'] != 0 &&
+            !in_array($res[0][$ldapKey], $details[$appKey]))) {
+            return PEAR::raiseError('Duplicate extension found.  Not saving changes.');
+        }
+
+        $entry = array(
+            'cn' => $details['name'],
+            'sn' => $details['name'],
+            'mail' => $details['email'],
+            'uid' => $details['email'],
+            'voiceMailbox' => $details['newextension'],
+            'voiceMailboxPin' => $details['mailboxpin'],
+            'context' => $context,
+            'asteriskUserDialOptions' => $details['dialopts'],
+        );
+
+        if (!empty ($details['telephonenumber'])) {
+            $entry['telephoneNumber'] = $details['telephonenumber'];
+        }
+
+        $validusers = &$this->getUsers($context);
+        if (!isset($validusers[$extension])) {
+            # Test to see if we're modifying an existing user that has
+            # no telephone system objectClasses and update that object/user
+            $rdn = $ldapKey.'='.$details[$appKey].',';
+            $branch = SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
+
+            # This test is something of a hack.  I want a cheap way to check
+            # for the existance of an object.  I don't want to do a full search
+            # so instead I compare that the dn equals the dn.  If the object
+            # exists then it'll return true.  If the object doesn't exist,
+            # it'll return error.  If it ever returns false something wierd
+            # is going on.
+            $res = @ldap_compare($this->_LDAP, $rdn.$branch,
+                    $ldapKey, $details[$appKey]);
+            if ($res === false) {
+                # We should never get here: a DN should ALWAYS match itself
+                return PEAR::raiseError("Internal Error: " . __FILE__ . " at " .
+                    __LINE__);
+            } elseif ($res === true) {
+                # The object/user exists but doesn't have the Asterisk
+                # objectClasses
+                $extension = $details['newextension'];
+
+                # $tmp is the minimal information required to establish
+                # an account in LDAP as required by the objectClasses.
+                # The entry will be fully populated below.
+                $tmp = array();
+                $tmp['objectClass'] = array(
+                    'asteriskUser',
+                    'asteriskVoiceMailbox'
+                );
+                $tmp['voiceMailbox'] = $extension;
+                $tmp['context'] = $context;
+                $res = @ldap_mod_replace($this->_LDAP, $rdn.$branch, $tmp);
+                if (!$res) {
+                    return PEAR::raiseError("Unable to modify the user: " .
+                        ldap_error($this->_LDAP));
+                }
+
+                # Populate the $validusers array to make the edit go smoothly
+                # below
+                $validusers[$extension] = array();
+                $validusers[$extension][$appKey] = $details[$appKey];
+
+                # The remainder of the work is done at the outside of the
+                # parent if() like a normal edit.
+
+            } elseif ($res === -1) {
+                # We must be adding a new user.
+                $entry['objectClass'] = array(
+                    'top',
+                    'person',
+                    'organizationalPerson',
+                    'inetOrgPerson',
+                    'hordePerson',
+                    'asteriskUser',
+                    'asteriskVoiceMailbox'
+                );
+
+                # Check to see if the maximum number of users for this context
+                # has been reached
+                $limits = $this->getLimits($context);
+                if (is_a($limits, "PEAR_Error")) {
+                    return $limits;
+                }
+                if (count($validusers) >= $limits['asteriskusersmax']) {
+                    return PEAR::raiseError('Maximum number of users reached.');
+                }
+
+                $res = @ldap_add($this->_LDAP, $rdn.$branch, $entry);
+                if (!$res) {
+                    return PEAR::raiseError('LDAP Add failed: ' .
+                        ldap_error($this->_LDAP));
+                }
+
+                return true;
+            } elseif (is_a($res, 'PEAR_Error')) {
+                # Some kind of internal error; not even sure if this is a
+                # possible outcome or not but I'll play it safe.
+                return $res;
+            }
+        }
+
+        # Anything after this point is an edit.
+
+        # Check to see if the object needs to be renamed (DN changed)
+        if ($validusers[$extension][$appKey] != $entry[$ldapKey]) {
+            $oldrdn = $ldapKey.'='.$validusers[$extension][$appKey];
+            $oldparent = SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
+            $newrdn = $ldapKey.'='.$entry[$ldapKey];
+            $res = @ldap_rename($this->_LDAP, "$oldrdn,$oldparent",
+                $newrdn, $oldparent, true);
+            if (!$res) {
+                return PEAR::raiseError('LDAP Rename failed: ' .
+                    ldap_error($this->_LDAP));
+            }
+        }
+
+        # Update the object/user
+        $dn = $ldapKey.'='.$entry[$ldapKey];
+        $dn .= ','.SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
+        $res = @ldap_modify($this->_LDAP, $dn, $entry);
+        if (!$res) {
+            return PEAR::raiseError('LDAP Modify failed: ' .
+                ldap_error($this->_LDAP));
+        }
+
+        # We must have been successful
+        return true;
+    }
+
+    /**
+     * Deletes a user from the LDAP tree
+     *
+     * @param string $context Context to delete the user from
+     * @param string $extension Extension of the user to be deleted
+     *
+     * @return boolean True on success, PEAR::Error object on error
+     */
+    public function deleteUser($context, $extension)
+    {
+        $ldapKey = &$this->_ldapKey;
+        $appKey = &$this->_appKey;
+
+        if (!Shout::checkRights("shout:contexts:$context:users",
+            PERMS_DELETE, 1)) {
+            return PEAR::raiseError("No permission to delete users in this " .
+                "context.");
+        }
+
+        $validusers = $this->getUsers($context);
+        if (!isset($validusers[$extension])) {
+            return PEAR::raiseError("That extension does not exist.");
+        }
+
+        $dn = "$ldapKey=".$validusers[$extension][$appKey];
+        $dn .= ',' . SHOUT_USERS_BRANCH . ',' . $this->_params['basedn'];
+
+        $res = @ldap_delete($this->_LDAP, $dn);
+        if (!$res) {
+            return PEAR::raiseError("Unable to delete $extension from " .
+                "$context: " . ldap_error($this->_LDAP));
+        }
+        return true;
+    }
+
+
+    /* Needed because uksort can't take a classed function as its callback arg */
+    protected function _sortexten($e1, $e2)
+    {
+        print "$e1 and $e2\n";
+        $ret =  Shout::extensort($e1, $e2);
+        print "returning $ret";
+        return $ret;
+    }
+
+    /**
+     * Attempts to open a connection to the LDAP server.
+     *
+     * @return boolean    True on success; exits (Horde::fatal()) on error.
+     *
+     * @access private
+     */
+    protected function _connect()
+    {
+        if ($this->_connected) {
+            return;
+        }
+
+        if (!Horde_Util::extensionExists('ldap')) {
+            throw new Horde_Exception('Required LDAP extension not found.');
+        }
+
+        Horde::assertDriverConfig($this->_params, $this->_params['class'],
+            array('hostspec', 'basedn', 'writedn'));
+
+        /* Open an unbound connection to the LDAP server */
+        $conn = ldap_connect($this->_params['hostspec'], $this->_params['port']);
+        if (!$conn) {
+             Horde::logMessage(
+                sprintf('Failed to open an LDAP connection to %s.',
+                        $this->_params['hostspec']),
+                __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+        }
+
+        /* Set hte LDAP protocol version. */
+        if (isset($this->_params['version'])) {
+            $result = ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION,
+                                       $this->_params['version']);
+            if ($result === false) {
+                Horde::logMessage(
+                    sprintf('Set LDAP protocol version to %d failed: [%d] %s',
+                            $this->_params['version'],
+                            ldap_errno($conn),
+                            ldap_error($conn)),
+                    __FILE__, __LINE__, PEAR_LOG_WARNING);
+                throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn));
+            }
+        }
+
+        /* Start TLS if we're using it. */
+        if (!empty($this->_params['tls'])) {
+            if (!@ldap_start_tls($conn)) {
+                Horde::logMessage(
+                    sprintf('STARTTLS failed: [%d] %s',
+                            @ldap_errno($this->_ds),
+                            @ldap_error($this->_ds)),
+                    __FILE__, __LINE__, PEAR_LOG_ERR);
+            }
+        }
+
+        /* If necessary, bind to the LDAP server as the user with search
+         * permissions. */
+        if (!empty($this->_params['searchdn'])) {
+            $bind = ldap_bind($conn, $this->_params['searchdn'],
+                              $this->_params['searchpw']);
+            if ($bind === false) {
+                Horde::logMessage(
+                    sprintf('Bind to server %s:%d with DN %s failed: [%d] %s',
+                            $this->_params['hostspec'],
+                            $this->_params['port'],
+                            $this->_params['searchdn'],
+                            @ldap_errno($conn),
+                            @ldap_error($conn)),
+                    __FILE__, __LINE__, PEAR_LOG_ERR);
+                throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn));
+            }
+        }
+
+        /* Store the connection handle at the instance level. */
+        $this->_LDAP = $conn;
+    }
+
+}
diff --git a/shout/lib/Driver/Sql.php b/shout/lib/Driver/Sql.php
new file mode 100644 (file)
index 0000000..8eaac44
--- /dev/null
@@ -0,0 +1,262 @@
+<?php
+
+class Shout_Driver_Sql extends Shout_Driver
+{
+    /**
+     * Handle for the current database connection.
+     * @var object $_db
+     */
+    protected $_db = null;
+
+    /**
+     * Boolean indicating whether or not we're connected to the LDAP
+     * server.
+     * @var boolean $_connected
+     */
+    protected $_connected = false;
+
+
+    /**
+    * Constructs a new Shout LDAP driver object.
+    *
+    * @param array  $params    A hash containing connection parameters.
+    */
+    function __construct($params = array())
+    {
+        parent::__construct($params);
+        $this->_connect();
+    }
+
+    public function getContexts()
+    {
+        $this->_connect();
+
+        $sql = 'SELECT context FROM %s';
+        $sql = sprintf($sql, $this->_params['table']);
+        $vars = array();
+
+        $result = $this->_db->query($sql, $vars);
+        if ($result instanceof PEAR_Error) {
+            throw Shout_Exception($result);
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        if ($row instanceof PEAR_Error) {
+            throw Shout_Exception($row);
+        }
+
+        $contexts = array();
+        while ($row && !($row instanceof PEAR_Error)) {
+            /* Add this new foo to the $_foo list. */
+            $contexts[] = $row['context'];
+
+            /* Advance to the new row in the result set. */
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        }
+
+         $result->free();
+         return $contexts;
+    }
+
+    /**
+     * Get a list of devices for a given context
+     *
+     * @param string $context    Context in which to search for devicess
+     *
+     * @return array  Array of devices within this context with their information
+     *
+     * @access private
+     */
+    public function getDevices($context)
+    {
+        $sql = 'SELECT id, name, alias, callerid, context, mailbox, host, permit, ' .
+               'nat, secret, disallow, allow FROM %s WHERE accountcode = ?';
+        $sql = sprintf($sql, $this->_params['table']);
+        $args = array($context);
+        $result = $this->_db->query($sql, $args);
+        if ($result instanceof PEAR_Error) {
+            throw new Shout_Exception($result);
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        if ($row instanceof PEAR_Error) {
+            throw new Shout_Exception($row);
+        }
+
+        $devices = array();
+        while ($row && !($row instanceof PEAR_Error)) {
+            // Asterisk uses the "name" field to indicate the registration
+            // identifier.  We use the field "alias" to put a friendly name on
+            // the device.  Thus devid -> name and name => alias
+            $devid = $row['name'];
+            $row['devid'] = $devid;
+            $row['name'] = $row['alias'];
+            unset($row['alias']);
+
+            // Trim off the context from the mailbox number
+            list($row['mailbox']) = explode('@', $row['mailbox']);
+
+            // Hide the DB internal ID from the front-end
+            unset($row['id']);
+
+            $devices[$devid] = $row;
+
+            /* Advance to the new row in the result set. */
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        }
+
+        $result->free();
+        return $devices;
+    }
+
+    /**
+     * Save a device (add or edit) to the backend.
+     *
+     * @param string $context  The context in which this device is valid
+     * @param array $info      Array of device details
+     */
+    public function saveDevice($context, $info)
+    {
+        // See getDevices() for an explanation of these conversions
+        $info['alias'] = $info['name'];
+        $info['mailbox'] = $info['mailbox'] . '@' . $context;
+
+        if ($info['devid']) {
+            // This is an edit
+            $info['name'] = $info['devid'];
+            $sql = 'UPDATE %s SET ';
+        } else {
+            // This is an add.  Generate a new unique ID and secret
+            $devid = $context . uniqid();
+            $secret = md5(uniqid(mt_rand));
+            $sql = 'INSERT INTO %s (name, accountcode, callerid, mailbox, ' .
+                   'secret, alias, canreinvite, nat, type) ' .
+                   ' VALUES (?, ?, ?, ?, ?, ?, "no", "yes", "peer")';
+
+        }
+
+
+    }
+
+    /**
+     * Get a list of users valid for the contexts
+     *
+     * @param string $context Context on which to search
+     *
+     * @return array User information indexed by voice mailbox number
+     */
+    public function getExtensions($context)
+    {
+        throw new Shout_Exception("Not implemented yet.");
+    }
+
+    /**
+     * Save a user to the LDAP tree
+     *
+     * @param string $context Context to which the user should be added
+     *
+     * @param string $extension Extension to be saved
+     *
+     * @param array $userdetails Phone numbers, PIN, options, etc to be saved
+     *
+     * @return TRUE on success, PEAR::Error object on error
+     */
+    public function saveExtension($context, $extension, $userdetails)
+    {
+        throw new Shout_Exception("Not implemented.");
+    }
+
+    /**
+     * Deletes a user from the LDAP tree
+     *
+     * @param string $context Context to delete the user from
+     * @param string $extension Extension of the user to be deleted
+     *
+     * @return boolean True on success, PEAR::Error object on error
+     */
+    public function deleteExtension($context, $extension)
+    {
+        throw new Shout_Exception("Not implemented.");
+    }
+
+    /**
+     * Attempts to open a persistent connection to the SQL server.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _connect()
+    {
+        if ($this->_connected) {
+            return;
+        }
+
+        Horde::assertDriverConfig($this->_params, $this->_params['class'],
+                                  array('phptype', 'charset', 'table'));
+
+        if (!isset($this->_params['database'])) {
+            $this->_params['database'] = '';
+        }
+        if (!isset($this->_params['username'])) {
+            $this->_params['username'] = '';
+        }
+        if (!isset($this->_params['hostspec'])) {
+            $this->_params['hostspec'] = '';
+        }
+
+        /* Connect to the SQL server using the supplied parameters. */
+        $this->_write_db = DB::connect($this->_params,
+                                       array('persistent' => !empty($this->_params['persistent'])));
+        if ($this->_write_db instanceof PEAR_Error) {
+            throw Horde_Exception($this->_write_db);
+        }
+
+        // Set DB portability options.
+        switch ($this->_write_db->phptype) {
+        case 'mssql':
+            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+            break;
+
+        default:
+            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+        }
+
+        /* Check if we need to set up the read DB connection seperately. */
+        if (!empty($this->_params['splitread'])) {
+            $params = array_merge($this->_params, $this->_params['read']);
+            $this->_db = DB::connect($params,
+                                     array('persistent' => !empty($params['persistent'])));
+            if ($this->_db instanceof PEAR_Error) {
+                throw Horde_Exception($this->_db);
+            }
+
+            // Set DB portability options.
+            switch ($this->_db->phptype) {
+            case 'mssql':
+                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+                break;
+
+            default:
+                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+            }
+
+        } else {
+            /* Default to the same DB handle for the writer too. */
+            $this->_db = $this->_write_db;
+        }
+
+        $this->_connected = true;
+    }
+
+    /**
+     * Disconnects from the SQL server and cleans up the connection.
+     */
+    protected function _disconnect()
+    {
+        if ($this->_connected) {
+            $this->_connected = false;
+            $this->_db->disconnect();
+            $this->_write_db->disconnect();
+        }
+    }
+    
+}
diff --git a/shout/lib/Exception.php b/shout/lib/Exception.php
new file mode 100644 (file)
index 0000000..301bcf7
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+class Shout_Exception extends Horde_Exception
+{
+}
\ No newline at end of file
diff --git a/shout/lib/Forms/DeviceForm.php b/shout/lib/Forms/DeviceForm.php
new file mode 100644 (file)
index 0000000..951738c
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * $Id: ExtensionForm.php 502 2009-12-21 04:01:12Z bklang $
+ *
+ * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file LICENSE for license information (GPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/gpl.php.
+ *
+ * @package Shout
+ */
+
+class DeviceDetailsForm extends Horde_Form {
+
+    function __construct(&$vars)
+    {
+        global $shout_extensions;
+
+        if ($vars->exists('devid')) {
+            $formtitle = "Edit Device";
+            $devid = $vars->get('devid');
+            $edit = true;
+        } else {
+            $formtitle = "Add Device";
+            $edit = false;
+        }
+
+        parent::__construct($vars, _("$formtitle - Context: $context"));
+        $this->addHidden('', 'action', 'text', true);
+        $vars->set('action', 'save');
+        if ($edit) {
+            $this->addHidden('', 'devid', 'text', true);
+
+        }
+        $this->addVariable(_("Device Name"), 'name', 'text', false);
+        $this->addVariable(_("Mailbox"), 'mailbox', 'int', false);
+        $this->addVariable(_("CallerID"), 'callerid', 'text', false);
+
+
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/shout/lib/Forms/ExtensionForm.php b/shout/lib/Forms/ExtensionForm.php
new file mode 100644 (file)
index 0000000..444319d
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005-2009 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file LICENSE for license information (GPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/gpl.php.
+ *
+ * @package Shout
+ */
+
+class ExtensionDetailsForm extends Horde_Form {
+
+    /**
+     * ExtensionDetailsForm constructor.
+     * 
+     * @global <type> $shout_extensions
+     * @param <type> $vars
+     * @return <type> 
+     */
+    function __construct(&$vars)
+    {
+        global $shout_extensions;
+        $context = $vars->get('context');
+        if ($vars->exists('extension')) {
+            $formtitle = "Edit User";
+            $extension = $vars->get('extension');
+        } else {
+            $formtitle = "Add User";
+        }
+
+        parent::__construct($vars, _("$formtitle - Context: $context"));
+        $this->addHidden('', 'action', 'text', true);
+        $vars->set('action', 'save');
+        $this->addHidden('', 'extension', 'int', true);
+        $vars->set('newextension', $extension);
+        $this->addVariable(_("Full Name"), 'name', 'text', true);
+        $this->addVariable(_("Extension"), 'newextension', 'int', true);
+        $this->addVariable(_("E-Mail Address"), 'email', 'email', true);
+        $this->addVariable(_("Pager E-Mail Address"), 'pageremail', 'email', false);
+        $this->addVariable(_("PIN"), 'mailboxpin', 'int', true);
+
+        return true;
+    }
+
+    /**
+     * Process this form, saving its information to the backend.
+     *
+     * @param string $context  Context in which to execute this save
+     * FIXME: is there a better way to get the $context and $shout_extensions?
+     */
+    function execute($context)
+    {
+        global $shout_extensions;
+
+        $extension = $this->vars->get('extension');
+
+        # FIXME: Input Validation (Text::??)
+        $details = array(
+            'newextension' => $vars->get('newextension'),
+            'name' => $vars->get('name'),
+            'mailboxpin' => $vars->get('mailboxpin'),
+            'email' => $vars->get('email'),
+        );
+
+        $res = $shout_extensions->saveExtension($context, $extension, $details);
+    }
+
+}
\ No newline at end of file
diff --git a/shout/lib/Shout.php b/shout/lib/Shout.php
new file mode 100644 (file)
index 0000000..8eed4aa
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+/**
+ * Shout:: defines an set of classes for the Shout application.
+ *
+ * $Id$
+ *
+ * Copyright 2005 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Ben Klang <ben@alkaloid.net>
+ * @version $Revision: 94 $
+ * @since   Shout 0.1
+ * @package Shout
+ */
+
+class Shout
+{
+    var $applist = array();
+    var $_applist_curapp = '';
+    var $_applist_curfield = '';
+
+    /**
+     * Build Shout's list of menu items.
+     *
+     * @access public
+     */
+    static public function getMenu($returnType = 'object')
+    {
+        global $conf, $context, $section, $action;
+
+        require_once 'Horde/Menu.php';
+
+        $menu = new Horde_Menu(HORDE_MENU_MASK_ALL);
+
+        $menu->add(Horde::applicationUrl('extensions.php'), _("Extensions"), "user.png");
+        $menu->add(Horde::applicationUrl('devices.php'), _("Devices"), "shout.png");
+        $menu->add(Horde::applicationUrl('routes.php'), _("Call Paths"));
+
+
+        if ($returnType == 'object') {
+            return $menu;
+        } else {
+            return $menu->render();
+        }
+    }
+
+    /**
+     * Generate the tabs at the top of each Shout pages
+     *
+     * @param &$vars Reference to the passed in variables
+     *
+     * @return object Horde_UI_Tabs
+     */
+    static public function getTabs($context, &$vars)
+    {
+        global $shout;
+        $perms = Horde_Perms::singleton();
+
+        $permprefix = 'shout:contexts:' . $context;
+
+        $tabs = new Horde_UI_Tabs('section', $vars);
+
+        if (Shout::checkRights($permprefix . ':extensions', null, 1)) {
+            $url = Horde::applicationUrl('extensions.php');
+            $tabs->addTab(_("_Extensions"), $url, 'extensions');
+        }
+
+        if (Shout::checkRights($permprefix . ':dialplan', null, 1)) {
+            $url = Horde::applicationUrl('dialplan.php');
+            $tabs->addTab(_("_Automated Attendant"), $url, 'dialplan');
+        }
+
+        if (Shout::checkRights($permprefix . ':conference', null, 1)) {
+            $url = Horde::applicationUrl('conference.php');
+            $tabs->addTab(_("_Conference Rooms"), $url, 'conference');
+        }
+
+       if (Shout::checkRights($permprefix . ':moh', null, 1)) {
+            $url = Horde::applicationUrl('moh.php');
+            $tabs->addTab(_("_Music on Hold"), $url, 'moh');
+        }
+
+        return $tabs;
+    }
+
+    /**
+     * Checks for the given permissions for the current user on the given
+     * permission.  Optionally check for higher-level permissions and ultimately
+     * test for superadmin priveleges.
+     *
+     * @param string $permname Name of the permission to check
+     *
+     * @param optional int $permmask Bitfield of permissions to check for
+     *
+     * @param options int $numparents Check for the same permissions this
+     *                                many levels up the tree
+     *
+     * @return boolean the effective permissions for the user.
+     */
+    static public function checkRights($permname, $permmask = null, $numparents = 0)
+    {
+        if (Horde_Auth::isAdmin()) { return true; }
+
+        $perms = Horde_Perms::singleton();
+        if ($permmask === null) {
+            $permmask = PERMS_SHOW|PERMS_READ;
+        }
+
+        # Default deny all permissions
+        $user = 0;
+        $superadmin = 0;
+
+        $superadmin = $perms->hasPermission('shout:superadmin',
+            Horde_Auth::getAuth(), $permmask);
+
+        while ($numparents >= 0) {
+            $tmpuser = $perms->hasPermission($permname,
+                Horde_Auth::getAuth(), $permmask);
+
+            $user = $user | $tmpuser;
+            if ($numparents > 0) {
+                $pos = strrpos($permname, ':');
+                if ($pos) {
+                    $permname = substr($permname, 0, $pos);
+                }
+            }
+            $numparents--;
+        }
+        $test = $superadmin | $user;
+$ret = ($test & $permmask) == $permmask;
+print "Shout::checkRights() returning $ret";
+        return ($test & $permmask) == $permmask;
+    }
+}
diff --git a/shout/lib/api.php b/shout/lib/api.php
new file mode 100644 (file)
index 0000000..ce6d59f
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Shout external API interface.
+ *
+ * $Id$
+ *
+ * This file defines Shout's external API interface. Other
+ * applications can interact with Shout through this API.
+ *
+ * @package Shout
+ */
+@define('SHOUT_BASE', dirname(__FILE__) . "/..");
+
+$_services['perms'] = array(
+    'args' => array(),
+    'type' => '{urn:horde}stringArray',
+);
+
+$_services['attributes'] = array(
+    'args' => array(),
+    'type' => '{urn:horde}stringArray',
+);
+
+function _shout_perms()
+{
+    static $perms = array();
+    if (!empty($perms)) {
+        return $perms;
+    }
+
+    @define('SHOUT_BASE', dirname(__FILE__) . '/..');
+    require_once SHOUT_BASE . '/lib/base.php';
+
+    $perms['tree']['shout']['superadmin'] = false;
+    $perms['title']['shout:superadmin'] = _("Super Administrator");
+
+    $contexts = $shout->getContexts();
+
+    $perms['tree']['shout']['contexts'] = false;
+    $perms['title']['shout:contexts'] = _("Contexts");
+
+    // Run through every contact source.
+    foreach ($contexts as $context => $contextInfo) {
+        $perms['tree']['shout']['contexts'][$context] = false;
+        $perms['title']['shout:contexts:' . $context] = $context;
+
+        foreach(
+            array(
+                'users' => 'Users',
+                'dialplan' => 'Dialplan',
+                'moh' => 'Music on Hold',
+                'conferences' => 'Conferencing',
+            )
+            as $module => $modname) {
+            $perms['tree']['shout']['contexts'][$context][$module] = false;
+            $perms['title']["shout:contexts:$context:$module"] = $modname;
+        }
+    }
+
+//     function _shout_getContexts($searchfilters = SHOUT_CONTEXT_ALL,
+//                          $filterperms = null)
+
+    return $perms;
+}
+
+function _shout_attributes()
+{
+    // See CONGREGATION_BASE/docs/api.txt for information on the structure
+    // of this array.
+    $shoutAttributes = array(
+        'description' => 'Phone System User Settings',
+        'attributes' => array(
+            'extension' => array(
+                'name' => 'Extension',
+                'description' => 'Phone System Extension (doubles as Voice Mailbox Number',
+                'type' => 'int',
+                'size' => 3,
+                'keys' => array(
+                    'ldap' => 'asteriskVoiceMailbox',
+                ),
+                'limit' => 1,
+                'required' => true,
+                'infoset' => 'basic',
+            ),
+
+            'mailboxpin' => array(
+                'name' => 'Mailbox PIN',
+                'description' => 'Voice Mailbox PIN',
+                'type' => 'int',
+                'size' => 12,
+                'keys' => array(
+                    'ldap' => 'asteriskVoiceMailboxPIN',
+                ),
+                'limit' => 1,
+                'required' => true,
+                'infoset' => 'basic',
+            ),
+
+            'phonenumbers' => array(
+                'name' => 'Telephone Numbers',
+                'description' => 'Dialout phone numbers',
+                'type' => 'cellphone', // WHY does Horde have cellphone but NOT
+                                       // telephone or just phonenumber???
+                'size' => 12,
+                'keys' => array(
+                    'ldap' => 'telephoneNumber',
+                ),
+                'limit' => 5,
+                'required' => true,
+                'infoset' => 'basic',
+            ),
+
+            'dialstring' => array(
+                'name' => 'Dial String',
+                'description' => 'Asterisk raw dial string',
+                'type' => 'cellphone', // WHY does Horde have cellphone but NOT
+                                       // telephone or just phonenumber???
+                'size' => 12,
+                'keys' => array(
+                    'ldap' => 'telephoneNumber',
+                ),
+                'limit' => 5,
+                'required' => true,
+                'infoset' => 'restricted',
+            ),
+        ),
+    );
+
+    return $shoutAttributes;
+}
diff --git a/shout/lib/base.php b/shout/lib/base.php
new file mode 100644 (file)
index 0000000..4ec1df0
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Shout base inclusion file.
+ *
+ * $Id$
+ *
+ * This file brings in all of the dependencies that every Shout
+ * script will need and sets up objects that all scripts use.
+ */
+
+if (!defined('SHOUT_BASE')) {
+    define('SHOUT_BASE', dirname(__FILE__). '/..');
+}
+
+if (!defined('HORDE_BASE')) {
+    /* If horde does not live directly under the app directory, the HORDE_BASE
+     * constant should be defined in config/horde.local.php. */
+    if (file_exists(SHOUT_BASE. '/config/horde.local.php')) {
+        include SHOUT_BASE . '/config/horde.local.php';
+    } else {
+        define('HORDE_BASE', SHOUT_BASE . '/..');
+    }
+}
+
+// Load the Horde Framework core, and set up inclusion paths.
+require_once HORDE_BASE . '/lib/core.php';
+
+$registry = &Horde_Registry::singleton();
+try {
+    $registry->pushApp('shout', array('check_perms' => true, 'logintasks' => true));
+} catch (Horde_Exception $e) {
+    Horde::authenticationFailureRedirect('shout', $e);
+}
+
+$conf = &$GLOBALS['conf'];
+@define('SHOUT_TEMPLATES', $registry->get('templates'));
+
+// Ensure Shout is properly configured before use
+$shout_configured = (@is_readable(SHOUT_BASE . '/config/conf.php'));
+if (!$shout_configured) {
+    Horde_Test::configFilesMissing('Shout', SHOUT_BASE, array('conf.php'));
+}
+
+$notification = Horde_Notification::singleton();
+$notification->attach('status');
+
+//// Shout base libraries.
+//require_once SHOUT_BASE . '/lib/Shout.php';
+//require_once SHOUT_BASE . '/lib/Driver.php';
+//
+//// Form libraries.
+//require_once 'Horde/Form.php';
+//require_once 'Horde/Form/Renderer.php';
+//
+//// Variable handling libraries
+//require_once 'Horde/Variables.php';
+//require_once 'Horde/Text/Filter.php';
+//
+//// UI classes.
+//require_once 'Horde/UI/Tabs.php';
+
+$shout_storage = Shout_Driver::factory('storage');
+$shout_extensions = Shout_Driver::factory('extensions');
+$shout_devices = Shout_Driver::factory('devices');
+
+$context = Horde_Util::getFormData('context');
+$section = Horde_Util::getFormData('section');
+
+try {
+    $contexts = $shout_storage->getContexts();
+} catch (Shout_Exception $e) {
+    $notification->push($e);
+    $contexts = false;
+}
+
+if (count($contexts) == 1) {
+    // Default to the user's only context
+    $context = $contexts[0];
+} elseif (!empty($context) && !in_array($context, $contexts)) {
+    $notification->push('You do not have permission to access that context.', 'horde.error');
+    $context = false;
+} elseif (!empty($context)) {
+    $notification->push("Please select a context to continue.", 'horde.info');
+    $context = false;
+}
+
+$_SESSION['shout']['context'] = $context;
\ No newline at end of file
diff --git a/shout/lib/shoutAtts.php b/shout/lib/shoutAtts.php
new file mode 100644 (file)
index 0000000..cc70a90
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/* 
+ *
+ * Copyright 2005 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+$shoutAttributes = array();
+$shoutAttributes['description'] = 'Phone System Options';
+$shoutAttributes['permsNode'] = 'shout:contexts:somethingOrOther';
+$shoutAttributes['attributes']['extension']['description'] = 'Internal Phone Extension';
+$shoutAttributes['attributes']['extension']['type'] = 'integer';
+$shoutAttributes['attributes']['extension']['size'] = 3; // max length for this particular string
+$shoutAttributes['attributes']['extension']['ldapKey'] = 'voiceextensionsomethingIhavenoidea';
+
+$shoutAttributes['attributes']['mailboxpin']['description'] = 'Mailbox PIN';
+$shoutAttributes['attributes']['mailboxpin']['type'] = 'integer';
+$shoutAttributes['attributes']['mailboxpin']['size'] = 4;
+$shoutAttributes['attributes']['mailboxpin']['ldapKey'] = 'voicemailboxpin';
+
+$shoutAttributes['attributes']['phonenumbers']['description'] = 'Phone Numbers';
+$shoutAttributes['attributes']['phonenumbers']['type'] = 'array';
+$shoutAttributes['attributes']['phonenumbers']['size'] = 1;
+$shoutAttributes['attributes']['phonenumbers']['arrayType'] = 'string';
+$shoutAttributes['attributes']['phonenumbers']['ldapKey'] = 'asteriskuserphonenumbers';
+
+?>
diff --git a/shout/lib/version.php b/shout/lib/version.php
new file mode 100644 (file)
index 0000000..b855f6a
--- /dev/null
@@ -0,0 +1 @@
+<?php define('SHOUT_VERSION', '0.1-svn') ?>
\ No newline at end of file
diff --git a/shout/moh.php b/shout/moh.php
new file mode 100644 (file)
index 0000000..3c89ebd
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package shout
+ */
+if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
+    header('Location: /');
+    exit();
+}
+
+$title = _('Music on Hold');
+
+require SHOUT_TEMPLATES . '/common-header.inc';
+require SHOUT_TEMPLATES . '/menu.inc';
+
+$notification->notify();
+
+$tabs->render($section);
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/shout/security.php b/shout/security.php
new file mode 100644 (file)
index 0000000..11f3126
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * $Id$
+ *
+ * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package shout
+ */
+
+if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
+    header('Location: /');
+    exit();
+}
+
+$title = _('Security');
+
+require SHOUT_TEMPLATES . '/common-header.inc';
+require SHOUT_TEMPLATES . '/menu.inc';
+
+$notification->notify();
+
+$tabs->render($section);
+
+Shout::getApplist();
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
\ No newline at end of file
diff --git a/shout/templates/common-header.inc b/shout/templates/common-header.inc
new file mode 100644 (file)
index 0000000..0d03352
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+     if (isset($language)) {
+     header('Content-type: text/html; charset=' . Horde_NLS::getCharset());
+     header('Vary: Accept-Language');
+     }
+     ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+          "DTD/xhtml1-transitional.dtd">
+<!--          Shout: Copyright 2005-2006, Ben Klang.           -->
+<!-- Horde Project: http://horde.org/ | Shout: http://projects.alkaloid.net/ -->
+<!--          Horde Licenses: http://www.horde.org/licenses/           -->
+<?php echo !empty($language) ? '<html lang="' . strtr($language, '_', '-') .
+                                      '">' : '<html>' ?>
+        <head>
+            <?php
+
+                 $page_title = $GLOBALS['registry']->get('name');
+            if (!empty($title)) $page_title .= ' :: ' . $title;
+            if (!empty($refresh_time) && !empty($refresh_url)) {
+            echo "<meta http-equiv=\"refresh\"
+                        content=\"$refresh_time;url=$refresh_url\">\n";
+                }
+
+                Horde::includeScriptFiles();
+
+                ?>
+                <title><?php echo $page_title ?></title>
+                <?php echo Horde::includeStylesheetFiles() ?>
+</head>
+
+<body<?php if (Horde_Util::nonInputVar('bodyClass')) echo ' class="' . $bodyClass .
+'"' ?>>
diff --git a/shout/templates/content_page b/shout/templates/content_page
new file mode 100644 (file)
index 0000000..66fd553
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+
+if (!defined(SHOUT_BASE)) {
+    define(SHOUT_BASE, dirname($_SELF['PHP_SELF']));
+}
+
+require_once SHOUT_BASE."/lib/defines.php";
+require_once SHOUT_BASE."/lib/base.php";
\ No newline at end of file
diff --git a/shout/templates/context/contextline.inc b/shout/templates/context/contextline.inc
new file mode 100644 (file)
index 0000000..11c6251
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+echo Horde::link(
+    Util::addParameter(
+        Horde::applicationUrl("edit/context.php"), "context", $context));
+echo $context;
+echo "</a><br />";
\ No newline at end of file
diff --git a/shout/templates/devices/edit.inc b/shout/templates/devices/edit.inc
new file mode 100644 (file)
index 0000000..a95682f
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+$RENDERER->beginActive($Form->getTitle());
+$RENDERER->renderFormActive($Form, $vars);
+$RENDERER->submit();
+$RENDERER->end();
+$Form->close($RENDERER);
\ No newline at end of file
diff --git a/shout/templates/devices/list.inc b/shout/templates/devices/list.inc
new file mode 100644 (file)
index 0000000..49516c0
--- /dev/null
@@ -0,0 +1,40 @@
+<div class="header">
+    Context: <?php echo $context; ?>
+</div>
+
+<div id="userList" class="userList">
+    <table width="100%" cellspacing="0">
+        <tr>
+            <td class="uheader">Device ID</td>
+            <td class="uheader">Mailbox</td>
+            <td class="uheader">CallerID</td>
+        </tr>
+        <?php
+            $line = 0;
+            foreach ($devices as $devid => $info) {
+
+                $url = Horde::applicationUrl("devices.php");
+                $url = Horde_Util::addParameter($url,
+                    array(
+                        'devid' => $devid,
+                    )
+                );
+                $editurl = Horde_Util::addParameter($url, 'action', 'edit');
+                $deleteurl = Horde_Util::addParameter($url, 'action', 'delete');
+        ?>
+        <tr class="item">
+            <td style="width: 20%">
+                <?php echo Horde::link($editurl); echo $devid; ?></a>
+            </td>
+            <td style="width: 35%;">
+                <?php echo $info['mailbox']; ?>
+            </td>
+            <td style="width: 45%">
+                <?php echo $info['callerid']; ?>
+            </td>
+        </tr>
+        <?php
+            }
+        ?>
+    </table>
+</div>
diff --git a/shout/templates/dialplan/contexttree.inc b/shout/templates/dialplan/contexttree.inc
new file mode 100644 (file)
index 0000000..dafbe7e
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="header">
+    <div class="contextHeader">Context: <?php echo $context; ?></div>
+</div>
+
+<div id='contextTree'>
+<?php $tree->renderTree(true); ?>
+<br />
+<a href="#top" class="small">Back to Top</a>
+</div>
diff --git a/shout/templates/dialplan/dialplanlist.inc b/shout/templates/dialplan/dialplanlist.inc
new file mode 100644 (file)
index 0000000..7ede9d8
--- /dev/null
@@ -0,0 +1,56 @@
+<table width="95%" border="0" cellpadding="0" cellspacing="0" class="item">
+  <tr valign="bottom" class="header">
+   <td colspan="2"><font size="3">Context: <?php echo $context; ?></font></td>
+  </tr>
+  <tr class="body"><td colspan="2">&nbsp;</td></tr>
+  <tr class="body"><td colspan="2" align="center">
+  <table width="95%" border="0" cellpadding="0" cellspacing="0" class="item">
+  <?php
+    $line = 0;
+    if (isset($dialplan['extensions']) &&
+        (count($dialplan['extensions']) > 0)) {
+        foreach ($dialplan['extensions'] as $extension => $priorities) {
+            $extname = Shout::exten2name($extension);
+            ?>
+            <tr class="header">
+              <th colspan="2" class="header">
+                <?php
+                if (isset($extname)) {
+                    echo $extname;
+                } else {
+                    echo "Extension $extension";
+                }
+                $editurl = Horde::applicationUrl("dialplan.php");
+                $editurl = Util::addParameter($editurl, "context=$context");
+                $editurl = Util::addParameter($editurl, "action=edit");
+                $editurl = Util::addParameter($editurl, "section=dialplan");
+                $editurl = Util::addParameter($editurl, "extension=$extension");
+                ?>&nbsp;<a class="lighthint" href="<?php
+                    echo $editurl;
+                ?>">edit</a>
+              </th>
+            </tr>
+            <tr class="smallheader">
+              <th align="left">Priority</th>
+              <th align="left">Application</th>
+            </tr>
+            <?php
+            foreach ($priorities as $priority => $application) {
+                $rowcolor = $line % 2;
+                $line++;
+                ?>
+                <tr class="item<?php echo $rowcolor; ?>">
+                    <td align="center"><?php echo $priority; ?></td>
+                    <td align="left"><?php echo $application; ?></td>
+                </tr>
+                <?php
+            }
+            ?>
+            <tr class="body"><td colspan="2">&nbsp;</td></tr>
+            <?php
+        }
+    }
+  ?>
+  </table>
+  </td></tr>
+</>
\ No newline at end of file
diff --git a/shout/templates/dialplan/extensiondetail.inc b/shout/templates/dialplan/extensiondetail.inc
new file mode 100644 (file)
index 0000000..a7ba902
--- /dev/null
@@ -0,0 +1,56 @@
+    // -->
+    </script>
+    <?php
+}
+?>
+<table class="pList" cellspacing="0">
+                    <?php
+                    $p = 0;
+                    foreach($priorities as $priority => $data) {
+                        ?>
+                        <tr class="priority">
+                            <td class="pButtons"
+                                id="<?php echo "pButtons-$extension-$priority"; ?>"
+                                name="<?php echo "pButtons-$extension-$priority"; ?>">
+                                <span class="add"
+                                    onclick="javascript:dp.addPrio('<?php
+                                        echo $extension; ?>', '<?php echo $priority; ?>');">+</span>
+                                <span class="remove"
+                                    onclick="javascript:dp.delPrio('<?php
+                                        echo $extension; ?>', '<?php echo $priority; ?>');">-</span>
+                            </td>
+                            <td class="pElement"
+                                id="<?php echo "pNumber-$extension-$priority"; ?>"
+                                name="<?php echo "pNumber-$extension-$priority"; ?>"
+                                onclick="javascript:dp.activatePriority('<?php
+                                    echo $extension; ?>', '<?php
+                                    echo $priority; ?>')">
+                                <span class="priorityBox">
+                                    <?php echo $priority; ?>
+                                </span>
+                            </td>
+                            <td class="pElement"
+                                id="<?php echo "pApp-$extension-$priority"; ?>"
+                                name="<?php echo "pApp-$extension-$priority"; ?>">
+                                <span class="applicationBox">
+                                    <select
+                                        name="app[<?php echo $extension; ?>][<?php echo $priority; ?>]"
+                                        id="app[<?php echo $extension; ?>][<?php echo $priority; ?>]">
+                                        <option value="<?php echo $data['application']; ?>">
+                                            <?php echo $data['application']; ?></option>
+                                    </select>
+                                </span>
+                            </td>
+                            <td class="pElement"
+                                id="<?php echo "pArgs-$extension-$priority"; ?>"
+                                name="<?php echo "pArgs-$extension-$priority"; ?>">
+                                <span class="argBox">
+                                    <?php echo $data['args']; ?>
+                                </span>
+                            </td>
+                        </tr>
+                        <?php
+                        $p++;
+                    }
+                    ?>
+                </table>
\ No newline at end of file
diff --git a/shout/templates/dialplan/manager.inc b/shout/templates/dialplan/manager.inc
new file mode 100644 (file)
index 0000000..6cf4e6c
--- /dev/null
@@ -0,0 +1,14 @@
+<div class="header">
+    <div class="contextHeader">Context: <?php echo $context; ?></div>
+</div>
+
+<?php
+$dpgui->renderNavTree();
+$dpgui->generateAppList();
+$dpgui->renderExtensions();
+?>
+<script language="javascript" type="text/javascript">
+<!--
+// shout_dialplan_object_x.drawInitial();
+//-->
+</script>
\ No newline at end of file
diff --git a/shout/templates/dialplan/priority-form-begin.inc b/shout/templates/dialplan/priority-form-begin.inc
new file mode 100644 (file)
index 0000000..3efb743
--- /dev/null
@@ -0,0 +1,7 @@
+<div id="_section___priority<?php echo $i; ?>" style="display:block;">
+    <table class="item" cellspacing="0">
+        <tr valign="top">
+            <td colspan="2" align="center" class="smallheader">
+                Priorities
+            </td>
+        </tr>
\ No newline at end of file
diff --git a/shout/templates/dialplan/priority-form-end.inc b/shout/templates/dialplan/priority-form-end.inc
new file mode 100644 (file)
index 0000000..55d12e0
--- /dev/null
@@ -0,0 +1,2 @@
+    </table>
+</div>
\ No newline at end of file
diff --git a/shout/templates/dialplan/priority-form-line.inc b/shout/templates/dialplan/priority-form-line.inc
new file mode 100644 (file)
index 0000000..123df5a
--- /dev/null
@@ -0,0 +1,11 @@
+<tr valign="top">
+    <td width="15%" align="right" class="item<?php echo $i % 2; ?>">
+        <input type="text"
+            name="priority[<?php echo $i; ?>]" value="<?php echo $priority; ?>"
+            size="3" id="priority[<?php echo $i; ?>]" /></td>
+    <td class="item<?php echo $i % 2; ?>">
+        <input type="text" name="application[<?php echo $i; ?>]"
+            size="40" value="<?php echo $application; ?>"
+            id="application[<?php echo $i; ?>]" />
+    </td>
+</tr>
\ No newline at end of file
diff --git a/shout/templates/extensions/edit.inc b/shout/templates/extensions/edit.inc
new file mode 100644 (file)
index 0000000..a95682f
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+$RENDERER->beginActive($Form->getTitle());
+$RENDERER->renderFormActive($Form, $vars);
+$RENDERER->submit();
+$RENDERER->end();
+$Form->close($RENDERER);
\ No newline at end of file
diff --git a/shout/templates/extensions/list.inc b/shout/templates/extensions/list.inc
new file mode 100644 (file)
index 0000000..4b00d15
--- /dev/null
@@ -0,0 +1,48 @@
+<div class="header">
+    Context: <?php echo $context; ?>
+</div>
+
+<div id="userList" class="userList">
+    <table width="100%" cellspacing="0">
+        <tr>
+            <td class="uheader">Extension</td>
+            <td class="uheader">Name</td>
+            <td class="uheader">E-Mail Address</td>
+        </tr>
+        <?php
+            $line = 0;
+            foreach ($extensions as $extension => $info) {
+
+                $url = Horde::applicationUrl("extensions.php");
+                $url = Horde_Util::addParameter($url,
+                    array(
+                        'extension' => $extension,
+                    )
+                );
+                $editurl = Horde_Util::addParameter($url, 'action', 'edit');
+                $deleteurl = Horde_Util::addParameter($url, 'action', 'delete');
+        ?>
+        <tr class="item">
+            <td style="width: 20%">
+                <?php echo Horde::link($editurl); echo $extension; ?></a>
+            </td>
+            <td style="width: 35%;">
+                <?php echo Horde::link($editurl); echo $info['name']; ?></a>
+                <?php
+                foreach ($info['devices'] as $device) {
+                    echo Horde::img('shout.png');
+                }
+                foreach ($info['numbers'] as $number) {
+                    echo Horde::img('telephone-pole.png');
+                }
+                ?>
+            </td>
+            <td style="width: 45%">
+                <?php echo $info['email']; ?>
+            </td>
+        </tr>
+        <?php
+            }
+        ?>
+    </table>
+</div>
diff --git a/shout/templates/javascript/dialplan.js b/shout/templates/javascript/dialplan.js
new file mode 100644 (file)
index 0000000..b0f06c3
--- /dev/null
@@ -0,0 +1,429 @@
+/**
+ * Shout Dialplan Javascript Class
+ *
+ * Provides the javascript class to create dynamic dialplans
+ *
+ * Copyright 2005 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you did not
+ * receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * $Id$
+ *
+ * @author  Ben Klang <ben@alkaloid.net>
+ * @package Shout
+ * @since   Shout 0.1
+ */
+function Dialplan(instanceName)
+{
+    this._instanceName = instanceName;
+    this.dp = new Array();
+    this.dp = eval('shout_dialplan_entry_'+instanceName);
+    this.applist = eval('shout_dialplan_applist_'+instanceName);
+    this.object = 'shout_dialplan_object_'+instanceName;
+    this.curExten = 0;
+    this.curPrio = 0;
+    this.prioActive = false;
+}
+
+Dialplan.prototype.highlightExten = function(exten)
+{
+    if (this.curExten && this.curExten != exten) {
+        this.dehighlightPrio();
+        this.dehighlightExten();
+    }
+
+    this.curExten = exten;
+    document.getElementById('eBox-' + exten).className = 'extensionBoxHighlight';
+    document.getElementById('pList-' + exten).className = 'pListHighlight';
+}
+
+Dialplan.prototype.dehighlightExten = function(exten)
+{
+    document.getElementById('eBox-' + this.curExten).className = 'extensionBox';
+    document.getElementById('pList-' + this.curExten).className = 'pList';
+    this.curExten = 0;
+}
+
+Dialplan.prototype.highlightPrio = function(exten, prio)
+{
+    if (exten != this.curExten) {
+        this.highlightExten(exten);
+    }
+    if (this.curPrio && prio != this.curPrio) {
+        this.dehighlightPrio();
+    }
+
+    this.curPrio = prio;
+    priority = document.getElementById('priority-'+exten+'-'+prio);
+    priority.className = 'priorityHighlight';
+    priority.onclick = priority.activate;
+    document.getElementById('pButtons-'+exten+'-'+prio).style['visibility'] = 'visible';
+}
+
+Dialplan.prototype.dehighlightPrio = function()
+{
+    this.deactivatePrio();
+    if (!this.curPrio) {
+        return true;
+    }
+
+    priority = document.getElementById('priority-'+this.curExten+'-'+this.curPrio);
+    priority.className = 'priority';
+    priority.onclick = priority.highlight;
+    document.getElementById('pButtons-'+this.curExten+'-'+this.curPrio).style['visibility'] = 'hidden';
+    this.curPrio = 0;
+    return true;
+}
+
+
+Dialplan.prototype.activatePrio = function(exten, prio)
+{
+    document.getElementById('pBox-'+exten+'-'+prio).style['display'] = 'none';
+    document.getElementById('pBoxInput-'+exten+'-'+prio).style['display'] = 'inline';
+    document.getElementById('pApp-'+exten+'-'+prio).style['display'] = 'none';
+    document.getElementById('pAppInput-'+exten+'-'+prio).style['display'] = 'inline';
+    document.getElementById('pArgs-'+exten+'-'+prio).style['display'] = 'none';
+    document.getElementById('pArgsInput-'+exten+'-'+prio).style['display'] = 'inline';
+    this.prioActive = true;
+}
+
+Dialplan.prototype.deactivatePrio = function()
+{
+    if (!this.prioActive) {
+        // Speed hack: If thie priority isn't active, don't go through the
+        // motions to deactivate it.
+        return true;
+    }
+    var dirty = false;
+    var pAppInput = document.getElementById('pAppInput-'+this.curExten+'-'+this.curPrio);
+    var pArgsInput = document.getElementById('pArgsInput-'+this.curExten+'-'+this.curPrio);
+
+    if (pAppInput.value != this.dp[this.curExten]['priorities'][this.curPrio]['application']) {
+        this.dp[this.curExten]['priorities'][this.curPrio]['application'] = pAppInput.value;
+        dirty = true;
+    }
+
+    if (pArgsInput != this.dp[this.curExten]['priorities'][this.curPrio]['args']) {
+        this.dp[this.curExten]['priorities'][this.curPrio]['args'] = pArgsInput.value;
+        dirty = true;
+    }
+
+    // Check to see if the priority number was updated
+    prio = Number(document.getElementById('pBoxInput-'+this.curExten+'-'+this.curPrio).value);
+    if (this.curPrio != prio) {
+        this.renumberPrio(prio);
+        this.curPrio = prio;
+        // Since we've just redrawn the prio table no sense in drawing it again
+        dirty = false;
+    }
+
+    // This test is purely a speed hack.  Redrawing the prio table is slower than simply resetting
+    // the status of the elements.  However if data has changed we are forced to redraw the prio table.
+    if (dirty) {
+        this.drawPrioTable(this.curExten);
+    } else {
+        document.getElementById('pBox-'+this.curExten+'-'+this.curPrio).style['display'] = 'inline';
+        document.getElementById('pBoxInput-'+this.curExten+'-'+this.curPrio).style['display'] = 'none';
+        document.getElementById('pApp-'+this.curExten+'-'+this.curPrio).style['display'] = 'inline';
+        document.getElementById('pAppInput-'+this.curExten+'-'+this.curPrio).style['display'] = 'none';
+        document.getElementById('pArgs-'+this.curExten+'-'+this.curPrio).style['display'] = 'inline';
+        document.getElementById('pArgsInput-'+this.curExten+'-'+this.curPrio).style['display'] = 'none';
+    }
+    this.prioActive = false;
+
+    return true;
+}
+
+Dialplan.prototype.drawPrioTable = function (exten)
+{
+    var table = '';
+    if (!exten) {
+        alert('Must first choose an extension to draw');
+        return false;
+    }
+
+    var pList = document.getElementById('pList-'+exten);
+
+    // Prune all children so we render a clean table
+    while(pList.childNodes[0]) {
+        pList.removeChild(pList.childNodes[0]);
+    }
+
+    for (var p in this.dp[exten]['priorities']) {
+//         table += '<div class="priority" id="priority-'+exten+'-'+p+'">\n';
+        var priority = document.createElement('div');
+        priority.className = 'priority';
+        priority.id = 'priority-' + exten + '-' + p;
+        // The next 5 lines are hijinks required to make the highlighting work properly.  We
+        // have to save a reference to this object in the pElement object so we can call back
+        // into the activate/deactivate routines.  We also have to save the prio and exten because
+        // the onclick assignment has to be a function reference which takes no arguments.
+        // See above and below comments disparaging javascript.
+        priority.dp = this;
+        priority.exten = exten;
+        priority.prio = p;
+        priority.highlight = function () { this.dp.highlightPrio(this.exten, this.prio); }
+        priority.activate = function () { this.dp.activatePrio(this.exten, this.prio); }
+        priority.onclick = priority.highlight;
+        pList.appendChild(priority);
+
+        var pButtons = document.createElement('span');
+        pButtons.className = 'pButtons';
+        pButtons.id = 'pButtons-' + exten + '-' + p;
+        priority.appendChild(pButtons);
+
+        var button = document.createElement('span');
+        button.className = 'add';
+        button.id = 'pButton-add-'+exten+'-'+p;
+        button.dp = this;
+        button.exten = exten;
+        button.prio = p;
+        button.addPrio = function () { this.dp.addPrio(this.exten, this.prio); }
+        button.onclick = button.addPrio;
+        button.innerHTML='+';
+        pButtons.appendChild(button);
+
+        var button = document.createElement('span');
+        button.className = 'remove';
+        button.id = 'pButton-del-'+exten+'-'+p;
+        button.dp = this;
+        button.exten = exten;
+        button.prio = p;
+        button.delPrio = function () { this.dp.delPrio(this.exten, this.prio); }
+        button.onclick = button.delPrio;
+        button.innerHTML='-';
+        pButtons.appendChild(button);
+
+        var pElement = document.createElement('span');
+        pElement.className = 'pElement';
+        priority.appendChild(pElement);
+
+        var pBox = document.createElement('span');
+        pBox.className = 'pBox';
+        pBox.id = 'pBox-'+exten+'-'+p;
+        pBox.style['display'] = 'inline';
+        pBox.innerHTML = p;
+        pElement.appendChild(pBox);
+
+        var pBoxInput = document.createElement('input');
+        pBoxInput.type = 'text';
+        pBoxInput.size = 3;
+        pBoxInput.id = 'pBoxInput-'+exten+'-'+p;
+        pBoxInput.name = 'pBoxInput-'+exten+'-'+p;
+        pBoxInput.value = p;
+        pBoxInput.maxlength = 3;
+        pBoxInput.style['display'] = 'none';
+        pBoxInput.dp = this;
+        pBoxInput.exten = exten;
+        pBoxInput.prio = p;
+        pBoxInput.onblur = pBoxInput.deactivate;
+        pBoxInput.deactivate = function () { this.dp.deactivatePriority(); }
+        pElement.appendChild(pBoxInput);
+
+        var pElement = document.createElement('span');
+        pElement.className = 'pElement';
+        priority.appendChild(pElement);
+
+        var pApp = document.createElement('span');
+        pApp.className = 'pApp';
+        pApp.id = 'pApp-' + exten + '-' + p;
+        pApp.style['display'] = 'inline';
+        pElement.appendChild(pApp);
+        pApp.innerHTML = this.dp[exten]['priorities'][p]['application'];
+
+        var pAppInput = document.createElement('select');
+        pAppInput.className = 'pAppInput';
+        pAppInput.id = 'pAppInput-' + exten + '-' + p;
+        pAppInput.name = 'pAppInput-' + exten + '-' + p;
+        pAppInput.style['display'] ='none';
+        pElement.appendChild(pAppInput);
+        this.genAppList(pAppInput, this.dp[exten]['priorities'][p]['application']);
+
+        var pElement = document.createElement('span');
+        pElement.className = 'pElement';
+        priority.appendChild(pElement);
+
+        var pArgs = document.createElement('span');
+        pArgs.className = 'pArgs';
+        pArgs.id = 'pArgs-' + exten + '-' + p;
+        pArgs.innerHTML = this.dp[exten]['priorities'][p]['args'];
+        pElement.appendChild(pArgs);
+
+        var pArgsInput = document.createElement('input');
+        pArgsInput.className = 'pArgsInput';
+        pArgsInput.id = 'pArgsInput-' + exten + '-' + p;
+        pArgsInput.name = 'pArgsInput-' + exten + '-' + p;
+        pArgsInput.value = this.dp[exten]['priorities'][p]['args'];
+        pArgsInput.style['display'] = 'none';
+        pElement.appendChild(pArgsInput);
+    }
+    return true;
+}
+
+Dialplan.prototype.genAppList = function (selectObj, app)
+{
+    for (var a in this.applist) {
+        var o = document.createElement('option');
+        o.value = this.applist[a];
+        if (this.applist[a] == app) {
+            o.selected = true;
+        }
+        o.innerHTML = this.applist[a];
+        selectObj.appendChild(o);
+    }
+    return true;
+}
+
+Dialplan.prototype.addExten = function (exten, extenName)
+{
+    this.dp[exten] = new Array();
+}
+
+Dialplan.prototype.addPrio = function(exten, prio)
+{
+    prio = Number(prio);
+    if (this.dp[exten]['priorities'][prio] != 'undefined') {
+        // Due to javascript's inability to remove an array element while maintaining
+        // associations, we copy the elements into a tmp array and ultimately replace
+        // the object's copy.  We will also have to sort the resulting array manually
+        // so it renders correctly.
+        var tmp = new Array();
+        var plist = new Array();
+        var i = 0;
+        var firstEmpty = prio + 1;
+
+        // Locate an empty priority.  We should not increment any priorities past this as
+        // the lower priorities will move to fill this hole.
+        while (this.dp[exten]['priorities'][firstEmpty]) {
+            firstEmpty++;
+        }
+
+        for (p in this.dp[exten]['priorities']) {
+            p = Number(p);
+            // Make a notch for the new priority by incrementing all priorities greater
+            // than the requested one.  Try to exclude error handling priorities
+            // which are unrelated to the changed extension.  See README for
+            // more information.
+            // TODO: Make a decision about whether this is the best way to handle
+            // error handling priorities.
+            if (p > prio && (p < prio + 90 || p > prio + 100) && p < firstEmpty) {
+                tmp[p + 1] = this.dp[exten]['priorities'][p];
+                plist[i++] = p + 1;
+            } else {
+                tmp[p] = this.dp[exten]['priorities'][p];
+                plist[i++] = p;
+            }
+        }
+
+        // Seed the new priority
+        p = prio + 1;
+        tmp[p] = new Array();
+        tmp[p]['application'] = '';
+        tmp[p]['args'] = '';
+        plist[i] = p;
+
+
+        // Empty the original array
+        this.dp[exten]['priorities'] = new Array();
+
+        // Sort the priorities and put them back into the original array
+        plist.sort(this._numCompare);
+        for (i = 0; i < plist.length; i++) {
+            p = Number(plist[i]);
+            this.dp[exten]['priorities'][p] = tmp[p];
+        }
+    }
+
+    this.drawPrioTable(exten);
+    this.highlightPrio(exten, prio);
+    return true;
+}
+
+Dialplan.prototype.insertPrio = function(exten, prio)
+{
+    // Simple wrapper for addPrio()
+    // Create an empty slot.  Subtract one from the new prio because the
+    // behavior of addPrio is to append to the specified location.
+    return this.addPrio(exten, prio - 1);
+}
+
+Dialplan.prototype.delPrio = function(exten, prio)
+{
+    prio = Number(prio);
+    if (this.dp[exten]['priorities'][prio] != 'undefined') {
+        // The .length method on this array always reports number of priorities + 1;
+        // Haven't yet solved this mystery but the below test does work correctly.
+        if (this.dp[exten]['priorities'].length <= 2) {
+            alert('Extensions must have at least one priority');
+            return false;
+        }
+        // Due to javascript's inability to remove an array element while maintaining
+        // associations, we copy the elements into a tmp array and ultimately replace
+        // the object's copy.  We will also have to sort the resulting array manually
+        // so it renders correctly.
+        var tmp = new Array();
+        var plist = new Array();
+        var i = 0;
+        var p;
+
+        for (p in this.dp[exten]['priorities']) {
+            // Notch out the old priority by decrementing all priorities greater
+            // than the requested one.  Try to exclude error handling priorities
+            // which are unrelated to the changed extension.  See README for
+            // more information.
+            // TODO: Make a decision about whether this is the best way to handle
+            // error handling priorities.
+            p = Number(p);
+            if (p > prio && (p < prio + 90 || p > prio + 100)) {
+                tmp[p - 1] = this.dp[exten]['priorities'][p];
+                plist[i++] = p - 1;
+            } else if (p != prio) {
+                tmp[p] = this.dp[exten]['priorities'][p];
+                plist[i++] = p;
+            }
+        }
+
+        // Empty the original array
+        this.dp[exten]['priorities'] = new Array();
+
+        // Sort the priorities and put them back into the original array
+        plist.sort(this._numCompare);
+        for (i = 0; i < plist.length; i++) {
+            p = Number(plist[i]);
+            this.dp[exten]['priorities'][p] = tmp[p];
+        }
+    }
+
+    this.curPrio = 0;
+    this.drawPrioTable(exten);
+    return true;
+}
+
+Dialplan.prototype.renumberPrio = function(newPrio)
+{
+    // Copy the old prio to a temporary location for future use
+    var tmp = new Array();
+    var oldPrio = Number(this.curPrio);
+    tmp = this.dp[this.curExten]['priorities'][oldPrio];
+    newPrio = Number(newPrio);
+
+    // Empty out the old priority
+    this.delPrio(this.curExten, oldPrio);
+
+    this.insertPrio(this.curExten, newPrio);
+    // Copy the old priority into its new home
+    this.dp[this.curExten]['priorities'][newPrio] = tmp;
+    this.drawPrioTable(this.curExten);
+
+    // Highlight the renumbered priority
+    this.curPrio = newPrio;
+    this.highlightPrio(this.curExten, this.curPrio);
+
+    return true;
+}
+
+Dialplan.prototype._numCompare = function(a, b)
+{
+    return (a - b);
+}
\ No newline at end of file
diff --git a/shout/templates/menu.inc b/shout/templates/menu.inc
new file mode 100644 (file)
index 0000000..070329a
--- /dev/null
@@ -0,0 +1,68 @@
+<a name="top"></a>
+<?php
+$accesskey = $prefs->getValue('widget_accesskey') ?
+    Horde::getAccessKey(_("Select _Context")) : '';
+$menu_view = $prefs->getValue('menu_view');
+?>
+
+<div id="menu">
+
+<?php
+// Only show the context selector if there is more than one available context
+if (count($contexts) > 1) { ?>
+<script language="JavaScript" type="text/javascript">
+<!--
+var loading;
+function contextSubmit(clear)
+{
+
+    if (document.contextMenu.context[document.contextMenu.context.selectedIndex].name != '') {
+        if ((loading == null) || (clear != null)) {
+            loading = true;
+            document.contextMenu.submit();
+        }
+    }
+}
+// -->
+</script>
+ <form action="index.php" method="get" name="contextMenu">
+  <span style="float:right">
+   <?php Horde_Util::pformInput() ?>
+   <label for="context" accesskey="<?php echo $accesskey ?>">
+    <select id="context" name="context" onchange="contextSubmit()">
+     <?php
+        foreach ($contexts as $c) {
+            print "<option value=\"$c\"";
+            if ($c == $context) {
+                print " selected";
+            }
+            print ">$c</option>\n";
+        }
+     ?>
+    </select>
+   </label>
+   <?php
+     if (isset($section)) {
+         ?>
+         <input type="hidden" name="section" value="<?php echo $section; ?>" />
+         <?php
+     }
+    ?>
+  </span>
+ </form>
+
+ <div style="float:right">
+  <?php
+   $link = Horde::link('#', _("Select Context"), '', '', 'contextSubmit(true);
+    return false;');
+   printf('<ul><li>%s%s<br />%s</a></li></ul>',
+    $link, Horde::img('folders/folder_open.png'),
+    ($menu_view != 'icon') ?
+    Horde::highlightAccessKey(_("Select _Context"), $accesskey) : '');
+  ?>
+ </div>
+
+<?php } // if (count(contexts) > 1) ?>
+
+  <?php echo Shout::getMenu('string') ?>
+</div>
diff --git a/shout/templates/table-limiter-begin.inc b/shout/templates/table-limiter-begin.inc
new file mode 100644 (file)
index 0000000..91da037
--- /dev/null
@@ -0,0 +1 @@
+<table width="95%" border="0" cellpadding="0" cellspacing="0"><tr><td>
\ No newline at end of file
diff --git a/shout/templates/table-limiter-end.inc b/shout/templates/table-limiter-end.inc
new file mode 100644 (file)
index 0000000..2c0bb58
--- /dev/null
@@ -0,0 +1,3 @@
+    </td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/shout/themes/graphics/add-extension.gif b/shout/themes/graphics/add-extension.gif
new file mode 100644 (file)
index 0000000..c343e4b
Binary files /dev/null and b/shout/themes/graphics/add-extension.gif differ
diff --git a/shout/themes/graphics/add-user.gif b/shout/themes/graphics/add-user.gif
new file mode 100644 (file)
index 0000000..faac4f1
Binary files /dev/null and b/shout/themes/graphics/add-user.gif differ
diff --git a/shout/themes/graphics/shout.png b/shout/themes/graphics/shout.png
new file mode 100644 (file)
index 0000000..e819577
Binary files /dev/null and b/shout/themes/graphics/shout.png differ
diff --git a/shout/themes/graphics/telephone-pole.png b/shout/themes/graphics/telephone-pole.png
new file mode 100644 (file)
index 0000000..7187c95
Binary files /dev/null and b/shout/themes/graphics/telephone-pole.png differ
diff --git a/shout/themes/graphics/user.png b/shout/themes/graphics/user.png
new file mode 100644 (file)
index 0000000..8b2c994
Binary files /dev/null and b/shout/themes/graphics/user.png differ
diff --git a/shout/themes/screen.css b/shout/themes/screen.css
new file mode 100644 (file)
index 0000000..057a159
--- /dev/null
@@ -0,0 +1,189 @@
+.lighthint {
+    color: #fff;
+    font-size: 10px;
+  }
+
+
+.darkhint {
+    color: #700;
+    font-size: 10px;
+  }
+
+table {
+    width: 100%;
+}
+
+.header {
+    color: #fff;
+    background: #a22;
+    text-align: right;
+    font-style: italic;
+    font-weight: bold;
+    font-size: 14px;
+}
+
+.userList{
+    border: 1px solid #000;
+}
+
+.uheader{
+    background: #fff;
+    color: #a22;
+    text-align: left;
+    font-weight: bold;
+    font-size: 130%;
+    border-bottom: 1px dashed #000;
+}
+
+#contextTree {
+    width: 180px;
+    left: 12px;
+    position: fixed;
+    top: 108px;
+    background: #fbb;
+    border: 1px solid #a22;
+    padding: 5px;
+  }
+
+
+#extensionDetail {
+    left: 200px;
+    position: absolute;
+    top: 50px;
+    background: #ccc;
+    border: 1px solid #000;
+    padding: 15px;
+  }
+
+#extensionDetail .lightHint {
+    background: #888;
+    color: #ddd;
+}
+
+#extensionDetail .darkHint{
+     background: #797
+     color: #fff;
+}
+
+#extensionDetail .extension {
+    border: 2px solid #000;
+    background: inherit;
+    padding: 5px;
+    width: 450px;
+}
+
+#extensionDetail .extensionHighlight {
+    border: 2px solid #a22;
+    padding: 5px;
+    padding-bottom: 20px;
+    background: #fbb;
+}
+
+#extensionDetail .extensionBox {
+    background: #fff;
+    color: #a22;
+    font-weight: bold;
+    font-size: 14px;
+    border: 2px solid #787;
+    padding: 5px;
+}
+
+#extensionDetail .extensionBoxHighlight {
+    background: #fbb;
+    color: #000;
+    font-weight: bold;
+    font-size: 14px;
+    border: 2px solid #fff;
+    padding: 5px;
+}
+
+#extensionDetail .pList {
+    border: 1px solid #787;
+    background: inherit;
+    /*left: 10px;
+    position: relative;*/
+}
+
+#extensionDetail .pListHighlight{
+    border: 1px solid #fff;
+    background: #fbb;
+    /*left: 10px;
+    position: relative;*/
+}
+
+#extensionDetail .priority {
+    padding: 4px;
+    /*left: 20px;
+    position: relative;*/
+    background: inherit;
+    /*border: 1px solid #000;*/
+}
+#extensionDetail .priorityHighlight {
+    background: #ccf;
+    padding: 3px;
+    border: 1px solid #000;
+    /*text-decoration: none;
+    font-color: #000;*/
+}
+
+#extensionDetail .pElement {
+    background: inherit;
+    padding: 3px;
+}
+#extensionDetail .pElementHighlight {
+    background: #ccf;
+    padding: 3px;
+    border-bottom: 1px solid #000;
+    border-top: 1px solid #000;
+    text-decoration: none;
+    font-color: #000;
+}
+
+#extensionDetail .pBox {
+    background: #fff;
+    color: #a22;
+    padding: 3px;
+    border: 1px solid #000;
+    width: 120px;
+    height: 16px;
+    text-align: center;
+    vertical-align: middle;
+    text-decoration: underline;
+}
+
+#extensionDetail .add {
+    border: 1px solid #000;
+    font-size: 10px;
+    font-weight: bold;
+    padding: 1px;
+    padding-left: 5px;
+    padding-right: 5px;
+}
+
+#extensionDetail .remove {
+    border: 1px solid #000;
+    font-size: 10px;
+    font-weight: bold;
+    padding: 1px;
+    padding-left: 5px;
+    padding-right: 5px;
+}
+
+#extensionDetail .pButtons {
+    visibility: hidden;
+    padding: 1px;
+}
+
+#extensionDetail .pArgs {
+    border: 1px #777 dotted;
+    padding-left: 5px;
+    padding-right: 5px;
+}
+
+/*#extensionDetail .pButtonsHighlight {
+    visibility: visible;
+    background: #ccf;
+    padding: 3px;
+    border-bottom: 1px solid #000;
+    border-top: 1px solid #000;
+}*/
\ No newline at end of file
diff --git a/templates/common-header.inc b/templates/common-header.inc
deleted file mode 100644 (file)
index 0d03352..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-     if (isset($language)) {
-     header('Content-type: text/html; charset=' . Horde_NLS::getCharset());
-     header('Vary: Accept-Language');
-     }
-     ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-          "DTD/xhtml1-transitional.dtd">
-<!--          Shout: Copyright 2005-2006, Ben Klang.           -->
-<!-- Horde Project: http://horde.org/ | Shout: http://projects.alkaloid.net/ -->
-<!--          Horde Licenses: http://www.horde.org/licenses/           -->
-<?php echo !empty($language) ? '<html lang="' . strtr($language, '_', '-') .
-                                      '">' : '<html>' ?>
-        <head>
-            <?php
-
-                 $page_title = $GLOBALS['registry']->get('name');
-            if (!empty($title)) $page_title .= ' :: ' . $title;
-            if (!empty($refresh_time) && !empty($refresh_url)) {
-            echo "<meta http-equiv=\"refresh\"
-                        content=\"$refresh_time;url=$refresh_url\">\n";
-                }
-
-                Horde::includeScriptFiles();
-
-                ?>
-                <title><?php echo $page_title ?></title>
-                <?php echo Horde::includeStylesheetFiles() ?>
-</head>
-
-<body<?php if (Horde_Util::nonInputVar('bodyClass')) echo ' class="' . $bodyClass .
-'"' ?>>
diff --git a/templates/content_page b/templates/content_page
deleted file mode 100644 (file)
index 66fd553..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-if (!defined(SHOUT_BASE)) {
-    define(SHOUT_BASE, dirname($_SELF['PHP_SELF']));
-}
-
-require_once SHOUT_BASE."/lib/defines.php";
-require_once SHOUT_BASE."/lib/base.php";
\ No newline at end of file
diff --git a/templates/context/contextline.inc b/templates/context/contextline.inc
deleted file mode 100644 (file)
index 11c6251..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-echo Horde::link(
-    Util::addParameter(
-        Horde::applicationUrl("edit/context.php"), "context", $context));
-echo $context;
-echo "</a><br />";
\ No newline at end of file
diff --git a/templates/devices/edit.inc b/templates/devices/edit.inc
deleted file mode 100644 (file)
index a95682f..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-$RENDERER->beginActive($Form->getTitle());
-$RENDERER->renderFormActive($Form, $vars);
-$RENDERER->submit();
-$RENDERER->end();
-$Form->close($RENDERER);
\ No newline at end of file
diff --git a/templates/devices/list.inc b/templates/devices/list.inc
deleted file mode 100644 (file)
index 49516c0..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<div class="header">
-    Context: <?php echo $context; ?>
-</div>
-
-<div id="userList" class="userList">
-    <table width="100%" cellspacing="0">
-        <tr>
-            <td class="uheader">Device ID</td>
-            <td class="uheader">Mailbox</td>
-            <td class="uheader">CallerID</td>
-        </tr>
-        <?php
-            $line = 0;
-            foreach ($devices as $devid => $info) {
-
-                $url = Horde::applicationUrl("devices.php");
-                $url = Horde_Util::addParameter($url,
-                    array(
-                        'devid' => $devid,
-                    )
-                );
-                $editurl = Horde_Util::addParameter($url, 'action', 'edit');
-                $deleteurl = Horde_Util::addParameter($url, 'action', 'delete');
-        ?>
-        <tr class="item">
-            <td style="width: 20%">
-                <?php echo Horde::link($editurl); echo $devid; ?></a>
-            </td>
-            <td style="width: 35%;">
-                <?php echo $info['mailbox']; ?>
-            </td>
-            <td style="width: 45%">
-                <?php echo $info['callerid']; ?>
-            </td>
-        </tr>
-        <?php
-            }
-        ?>
-    </table>
-</div>
diff --git a/templates/dialplan/contexttree.inc b/templates/dialplan/contexttree.inc
deleted file mode 100644 (file)
index dafbe7e..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="header">
-    <div class="contextHeader">Context: <?php echo $context; ?></div>
-</div>
-
-<div id='contextTree'>
-<?php $tree->renderTree(true); ?>
-<br />
-<a href="#top" class="small">Back to Top</a>
-</div>
diff --git a/templates/dialplan/dialplanlist.inc b/templates/dialplan/dialplanlist.inc
deleted file mode 100644 (file)
index 7ede9d8..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-<table width="95%" border="0" cellpadding="0" cellspacing="0" class="item">
-  <tr valign="bottom" class="header">
-   <td colspan="2"><font size="3">Context: <?php echo $context; ?></font></td>
-  </tr>
-  <tr class="body"><td colspan="2">&nbsp;</td></tr>
-  <tr class="body"><td colspan="2" align="center">
-  <table width="95%" border="0" cellpadding="0" cellspacing="0" class="item">
-  <?php
-    $line = 0;
-    if (isset($dialplan['extensions']) &&
-        (count($dialplan['extensions']) > 0)) {
-        foreach ($dialplan['extensions'] as $extension => $priorities) {
-            $extname = Shout::exten2name($extension);
-            ?>
-            <tr class="header">
-              <th colspan="2" class="header">
-                <?php
-                if (isset($extname)) {
-                    echo $extname;
-                } else {
-                    echo "Extension $extension";
-                }
-                $editurl = Horde::applicationUrl("dialplan.php");
-                $editurl = Util::addParameter($editurl, "context=$context");
-                $editurl = Util::addParameter($editurl, "action=edit");
-                $editurl = Util::addParameter($editurl, "section=dialplan");
-                $editurl = Util::addParameter($editurl, "extension=$extension");
-                ?>&nbsp;<a class="lighthint" href="<?php
-                    echo $editurl;
-                ?>">edit</a>
-              </th>
-            </tr>
-            <tr class="smallheader">
-              <th align="left">Priority</th>
-              <th align="left">Application</th>
-            </tr>
-            <?php
-            foreach ($priorities as $priority => $application) {
-                $rowcolor = $line % 2;
-                $line++;
-                ?>
-                <tr class="item<?php echo $rowcolor; ?>">
-                    <td align="center"><?php echo $priority; ?></td>
-                    <td align="left"><?php echo $application; ?></td>
-                </tr>
-                <?php
-            }
-            ?>
-            <tr class="body"><td colspan="2">&nbsp;</td></tr>
-            <?php
-        }
-    }
-  ?>
-  </table>
-  </td></tr>
-</>
\ No newline at end of file
diff --git a/templates/dialplan/extensiondetail.inc b/templates/dialplan/extensiondetail.inc
deleted file mode 100644 (file)
index a7ba902..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-    // -->
-    </script>
-    <?php
-}
-?>
-<table class="pList" cellspacing="0">
-                    <?php
-                    $p = 0;
-                    foreach($priorities as $priority => $data) {
-                        ?>
-                        <tr class="priority">
-                            <td class="pButtons"
-                                id="<?php echo "pButtons-$extension-$priority"; ?>"
-                                name="<?php echo "pButtons-$extension-$priority"; ?>">
-                                <span class="add"
-                                    onclick="javascript:dp.addPrio('<?php
-                                        echo $extension; ?>', '<?php echo $priority; ?>');">+</span>
-                                <span class="remove"
-                                    onclick="javascript:dp.delPrio('<?php
-                                        echo $extension; ?>', '<?php echo $priority; ?>');">-</span>
-                            </td>
-                            <td class="pElement"
-                                id="<?php echo "pNumber-$extension-$priority"; ?>"
-                                name="<?php echo "pNumber-$extension-$priority"; ?>"
-                                onclick="javascript:dp.activatePriority('<?php
-                                    echo $extension; ?>', '<?php
-                                    echo $priority; ?>')">
-                                <span class="priorityBox">
-                                    <?php echo $priority; ?>
-                                </span>
-                            </td>
-                            <td class="pElement"
-                                id="<?php echo "pApp-$extension-$priority"; ?>"
-                                name="<?php echo "pApp-$extension-$priority"; ?>">
-                                <span class="applicationBox">
-                                    <select
-                                        name="app[<?php echo $extension; ?>][<?php echo $priority; ?>]"
-                                        id="app[<?php echo $extension; ?>][<?php echo $priority; ?>]">
-                                        <option value="<?php echo $data['application']; ?>">
-                                            <?php echo $data['application']; ?></option>
-                                    </select>
-                                </span>
-                            </td>
-                            <td class="pElement"
-                                id="<?php echo "pArgs-$extension-$priority"; ?>"
-                                name="<?php echo "pArgs-$extension-$priority"; ?>">
-                                <span class="argBox">
-                                    <?php echo $data['args']; ?>
-                                </span>
-                            </td>
-                        </tr>
-                        <?php
-                        $p++;
-                    }
-                    ?>
-                </table>
\ No newline at end of file
diff --git a/templates/dialplan/manager.inc b/templates/dialplan/manager.inc
deleted file mode 100644 (file)
index 6cf4e6c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<div class="header">
-    <div class="contextHeader">Context: <?php echo $context; ?></div>
-</div>
-
-<?php
-$dpgui->renderNavTree();
-$dpgui->generateAppList();
-$dpgui->renderExtensions();
-?>
-<script language="javascript" type="text/javascript">
-<!--
-// shout_dialplan_object_x.drawInitial();
-//-->
-</script>
\ No newline at end of file
diff --git a/templates/dialplan/priority-form-begin.inc b/templates/dialplan/priority-form-begin.inc
deleted file mode 100644 (file)
index 3efb743..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<div id="_section___priority<?php echo $i; ?>" style="display:block;">
-    <table class="item" cellspacing="0">
-        <tr valign="top">
-            <td colspan="2" align="center" class="smallheader">
-                Priorities
-            </td>
-        </tr>
\ No newline at end of file
diff --git a/templates/dialplan/priority-form-end.inc b/templates/dialplan/priority-form-end.inc
deleted file mode 100644 (file)
index 55d12e0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-    </table>
-</div>
\ No newline at end of file
diff --git a/templates/dialplan/priority-form-line.inc b/templates/dialplan/priority-form-line.inc
deleted file mode 100644 (file)
index 123df5a..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<tr valign="top">
-    <td width="15%" align="right" class="item<?php echo $i % 2; ?>">
-        <input type="text"
-            name="priority[<?php echo $i; ?>]" value="<?php echo $priority; ?>"
-            size="3" id="priority[<?php echo $i; ?>]" /></td>
-    <td class="item<?php echo $i % 2; ?>">
-        <input type="text" name="application[<?php echo $i; ?>]"
-            size="40" value="<?php echo $application; ?>"
-            id="application[<?php echo $i; ?>]" />
-    </td>
-</tr>
\ No newline at end of file
diff --git a/templates/extensions/edit.inc b/templates/extensions/edit.inc
deleted file mode 100644 (file)
index a95682f..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-$RENDERER->beginActive($Form->getTitle());
-$RENDERER->renderFormActive($Form, $vars);
-$RENDERER->submit();
-$RENDERER->end();
-$Form->close($RENDERER);
\ No newline at end of file
diff --git a/templates/extensions/list.inc b/templates/extensions/list.inc
deleted file mode 100644 (file)
index 4b00d15..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<div class="header">
-    Context: <?php echo $context; ?>
-</div>
-
-<div id="userList" class="userList">
-    <table width="100%" cellspacing="0">
-        <tr>
-            <td class="uheader">Extension</td>
-            <td class="uheader">Name</td>
-            <td class="uheader">E-Mail Address</td>
-        </tr>
-        <?php
-            $line = 0;
-            foreach ($extensions as $extension => $info) {
-
-                $url = Horde::applicationUrl("extensions.php");
-                $url = Horde_Util::addParameter($url,
-                    array(
-                        'extension' => $extension,
-                    )
-                );
-                $editurl = Horde_Util::addParameter($url, 'action', 'edit');
-                $deleteurl = Horde_Util::addParameter($url, 'action', 'delete');
-        ?>
-        <tr class="item">
-            <td style="width: 20%">
-                <?php echo Horde::link($editurl); echo $extension; ?></a>
-            </td>
-            <td style="width: 35%;">
-                <?php echo Horde::link($editurl); echo $info['name']; ?></a>
-                <?php
-                foreach ($info['devices'] as $device) {
-                    echo Horde::img('shout.png');
-                }
-                foreach ($info['numbers'] as $number) {
-                    echo Horde::img('telephone-pole.png');
-                }
-                ?>
-            </td>
-            <td style="width: 45%">
-                <?php echo $info['email']; ?>
-            </td>
-        </tr>
-        <?php
-            }
-        ?>
-    </table>
-</div>
diff --git a/templates/javascript/dialplan.js b/templates/javascript/dialplan.js
deleted file mode 100644 (file)
index b0f06c3..0000000
+++ /dev/null
@@ -1,429 +0,0 @@
-/**
- * Shout Dialplan Javascript Class
- *
- * Provides the javascript class to create dynamic dialplans
- *
- * Copyright 2005 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file COPYING for license information (GPL). If you did not
- * receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * $Id$
- *
- * @author  Ben Klang <ben@alkaloid.net>
- * @package Shout
- * @since   Shout 0.1
- */
-function Dialplan(instanceName)
-{
-    this._instanceName = instanceName;
-    this.dp = new Array();
-    this.dp = eval('shout_dialplan_entry_'+instanceName);
-    this.applist = eval('shout_dialplan_applist_'+instanceName);
-    this.object = 'shout_dialplan_object_'+instanceName;
-    this.curExten = 0;
-    this.curPrio = 0;
-    this.prioActive = false;
-}
-
-Dialplan.prototype.highlightExten = function(exten)
-{
-    if (this.curExten && this.curExten != exten) {
-        this.dehighlightPrio();
-        this.dehighlightExten();
-    }
-
-    this.curExten = exten;
-    document.getElementById('eBox-' + exten).className = 'extensionBoxHighlight';
-    document.getElementById('pList-' + exten).className = 'pListHighlight';
-}
-
-Dialplan.prototype.dehighlightExten = function(exten)
-{
-    document.getElementById('eBox-' + this.curExten).className = 'extensionBox';
-    document.getElementById('pList-' + this.curExten).className = 'pList';
-    this.curExten = 0;
-}
-
-Dialplan.prototype.highlightPrio = function(exten, prio)
-{
-    if (exten != this.curExten) {
-        this.highlightExten(exten);
-    }
-    if (this.curPrio && prio != this.curPrio) {
-        this.dehighlightPrio();
-    }
-
-    this.curPrio = prio;
-    priority = document.getElementById('priority-'+exten+'-'+prio);
-    priority.className = 'priorityHighlight';
-    priority.onclick = priority.activate;
-    document.getElementById('pButtons-'+exten+'-'+prio).style['visibility'] = 'visible';
-}
-
-Dialplan.prototype.dehighlightPrio = function()
-{
-    this.deactivatePrio();
-    if (!this.curPrio) {
-        return true;
-    }
-
-    priority = document.getElementById('priority-'+this.curExten+'-'+this.curPrio);
-    priority.className = 'priority';
-    priority.onclick = priority.highlight;
-    document.getElementById('pButtons-'+this.curExten+'-'+this.curPrio).style['visibility'] = 'hidden';
-    this.curPrio = 0;
-    return true;
-}
-
-
-Dialplan.prototype.activatePrio = function(exten, prio)
-{
-    document.getElementById('pBox-'+exten+'-'+prio).style['display'] = 'none';
-    document.getElementById('pBoxInput-'+exten+'-'+prio).style['display'] = 'inline';
-    document.getElementById('pApp-'+exten+'-'+prio).style['display'] = 'none';
-    document.getElementById('pAppInput-'+exten+'-'+prio).style['display'] = 'inline';
-    document.getElementById('pArgs-'+exten+'-'+prio).style['display'] = 'none';
-    document.getElementById('pArgsInput-'+exten+'-'+prio).style['display'] = 'inline';
-    this.prioActive = true;
-}
-
-Dialplan.prototype.deactivatePrio = function()
-{
-    if (!this.prioActive) {
-        // Speed hack: If thie priority isn't active, don't go through the
-        // motions to deactivate it.
-        return true;
-    }
-    var dirty = false;
-    var pAppInput = document.getElementById('pAppInput-'+this.curExten+'-'+this.curPrio);
-    var pArgsInput = document.getElementById('pArgsInput-'+this.curExten+'-'+this.curPrio);
-
-    if (pAppInput.value != this.dp[this.curExten]['priorities'][this.curPrio]['application']) {
-        this.dp[this.curExten]['priorities'][this.curPrio]['application'] = pAppInput.value;
-        dirty = true;
-    }
-
-    if (pArgsInput != this.dp[this.curExten]['priorities'][this.curPrio]['args']) {
-        this.dp[this.curExten]['priorities'][this.curPrio]['args'] = pArgsInput.value;
-        dirty = true;
-    }
-
-    // Check to see if the priority number was updated
-    prio = Number(document.getElementById('pBoxInput-'+this.curExten+'-'+this.curPrio).value);
-    if (this.curPrio != prio) {
-        this.renumberPrio(prio);
-        this.curPrio = prio;
-        // Since we've just redrawn the prio table no sense in drawing it again
-        dirty = false;
-    }
-
-    // This test is purely a speed hack.  Redrawing the prio table is slower than simply resetting
-    // the status of the elements.  However if data has changed we are forced to redraw the prio table.
-    if (dirty) {
-        this.drawPrioTable(this.curExten);
-    } else {
-        document.getElementById('pBox-'+this.curExten+'-'+this.curPrio).style['display'] = 'inline';
-        document.getElementById('pBoxInput-'+this.curExten+'-'+this.curPrio).style['display'] = 'none';
-        document.getElementById('pApp-'+this.curExten+'-'+this.curPrio).style['display'] = 'inline';
-        document.getElementById('pAppInput-'+this.curExten+'-'+this.curPrio).style['display'] = 'none';
-        document.getElementById('pArgs-'+this.curExten+'-'+this.curPrio).style['display'] = 'inline';
-        document.getElementById('pArgsInput-'+this.curExten+'-'+this.curPrio).style['display'] = 'none';
-    }
-    this.prioActive = false;
-
-    return true;
-}
-
-Dialplan.prototype.drawPrioTable = function (exten)
-{
-    var table = '';
-    if (!exten) {
-        alert('Must first choose an extension to draw');
-        return false;
-    }
-
-    var pList = document.getElementById('pList-'+exten);
-
-    // Prune all children so we render a clean table
-    while(pList.childNodes[0]) {
-        pList.removeChild(pList.childNodes[0]);
-    }
-
-    for (var p in this.dp[exten]['priorities']) {
-//         table += '<div class="priority" id="priority-'+exten+'-'+p+'">\n';
-        var priority = document.createElement('div');
-        priority.className = 'priority';
-        priority.id = 'priority-' + exten + '-' + p;
-        // The next 5 lines are hijinks required to make the highlighting work properly.  We
-        // have to save a reference to this object in the pElement object so we can call back
-        // into the activate/deactivate routines.  We also have to save the prio and exten because
-        // the onclick assignment has to be a function reference which takes no arguments.
-        // See above and below comments disparaging javascript.
-        priority.dp = this;
-        priority.exten = exten;
-        priority.prio = p;
-        priority.highlight = function () { this.dp.highlightPrio(this.exten, this.prio); }
-        priority.activate = function () { this.dp.activatePrio(this.exten, this.prio); }
-        priority.onclick = priority.highlight;
-        pList.appendChild(priority);
-
-        var pButtons = document.createElement('span');
-        pButtons.className = 'pButtons';
-        pButtons.id = 'pButtons-' + exten + '-' + p;
-        priority.appendChild(pButtons);
-
-        var button = document.createElement('span');
-        button.className = 'add';
-        button.id = 'pButton-add-'+exten+'-'+p;
-        button.dp = this;
-        button.exten = exten;
-        button.prio = p;
-        button.addPrio = function () { this.dp.addPrio(this.exten, this.prio); }
-        button.onclick = button.addPrio;
-        button.innerHTML='+';
-        pButtons.appendChild(button);
-
-        var button = document.createElement('span');
-        button.className = 'remove';
-        button.id = 'pButton-del-'+exten+'-'+p;
-        button.dp = this;
-        button.exten = exten;
-        button.prio = p;
-        button.delPrio = function () { this.dp.delPrio(this.exten, this.prio); }
-        button.onclick = button.delPrio;
-        button.innerHTML='-';
-        pButtons.appendChild(button);
-
-        var pElement = document.createElement('span');
-        pElement.className = 'pElement';
-        priority.appendChild(pElement);
-
-        var pBox = document.createElement('span');
-        pBox.className = 'pBox';
-        pBox.id = 'pBox-'+exten+'-'+p;
-        pBox.style['display'] = 'inline';
-        pBox.innerHTML = p;
-        pElement.appendChild(pBox);
-
-        var pBoxInput = document.createElement('input');
-        pBoxInput.type = 'text';
-        pBoxInput.size = 3;
-        pBoxInput.id = 'pBoxInput-'+exten+'-'+p;
-        pBoxInput.name = 'pBoxInput-'+exten+'-'+p;
-        pBoxInput.value = p;
-        pBoxInput.maxlength = 3;
-        pBoxInput.style['display'] = 'none';
-        pBoxInput.dp = this;
-        pBoxInput.exten = exten;
-        pBoxInput.prio = p;
-        pBoxInput.onblur = pBoxInput.deactivate;
-        pBoxInput.deactivate = function () { this.dp.deactivatePriority(); }
-        pElement.appendChild(pBoxInput);
-
-        var pElement = document.createElement('span');
-        pElement.className = 'pElement';
-        priority.appendChild(pElement);
-
-        var pApp = document.createElement('span');
-        pApp.className = 'pApp';
-        pApp.id = 'pApp-' + exten + '-' + p;
-        pApp.style['display'] = 'inline';
-        pElement.appendChild(pApp);
-        pApp.innerHTML = this.dp[exten]['priorities'][p]['application'];
-
-        var pAppInput = document.createElement('select');
-        pAppInput.className = 'pAppInput';
-        pAppInput.id = 'pAppInput-' + exten + '-' + p;
-        pAppInput.name = 'pAppInput-' + exten + '-' + p;
-        pAppInput.style['display'] ='none';
-        pElement.appendChild(pAppInput);
-        this.genAppList(pAppInput, this.dp[exten]['priorities'][p]['application']);
-
-        var pElement = document.createElement('span');
-        pElement.className = 'pElement';
-        priority.appendChild(pElement);
-
-        var pArgs = document.createElement('span');
-        pArgs.className = 'pArgs';
-        pArgs.id = 'pArgs-' + exten + '-' + p;
-        pArgs.innerHTML = this.dp[exten]['priorities'][p]['args'];
-        pElement.appendChild(pArgs);
-
-        var pArgsInput = document.createElement('input');
-        pArgsInput.className = 'pArgsInput';
-        pArgsInput.id = 'pArgsInput-' + exten + '-' + p;
-        pArgsInput.name = 'pArgsInput-' + exten + '-' + p;
-        pArgsInput.value = this.dp[exten]['priorities'][p]['args'];
-        pArgsInput.style['display'] = 'none';
-        pElement.appendChild(pArgsInput);
-    }
-    return true;
-}
-
-Dialplan.prototype.genAppList = function (selectObj, app)
-{
-    for (var a in this.applist) {
-        var o = document.createElement('option');
-        o.value = this.applist[a];
-        if (this.applist[a] == app) {
-            o.selected = true;
-        }
-        o.innerHTML = this.applist[a];
-        selectObj.appendChild(o);
-    }
-    return true;
-}
-
-Dialplan.prototype.addExten = function (exten, extenName)
-{
-    this.dp[exten] = new Array();
-}
-
-Dialplan.prototype.addPrio = function(exten, prio)
-{
-    prio = Number(prio);
-    if (this.dp[exten]['priorities'][prio] != 'undefined') {
-        // Due to javascript's inability to remove an array element while maintaining
-        // associations, we copy the elements into a tmp array and ultimately replace
-        // the object's copy.  We will also have to sort the resulting array manually
-        // so it renders correctly.
-        var tmp = new Array();
-        var plist = new Array();
-        var i = 0;
-        var firstEmpty = prio + 1;
-
-        // Locate an empty priority.  We should not increment any priorities past this as
-        // the lower priorities will move to fill this hole.
-        while (this.dp[exten]['priorities'][firstEmpty]) {
-            firstEmpty++;
-        }
-
-        for (p in this.dp[exten]['priorities']) {
-            p = Number(p);
-            // Make a notch for the new priority by incrementing all priorities greater
-            // than the requested one.  Try to exclude error handling priorities
-            // which are unrelated to the changed extension.  See README for
-            // more information.
-            // TODO: Make a decision about whether this is the best way to handle
-            // error handling priorities.
-            if (p > prio && (p < prio + 90 || p > prio + 100) && p < firstEmpty) {
-                tmp[p + 1] = this.dp[exten]['priorities'][p];
-                plist[i++] = p + 1;
-            } else {
-                tmp[p] = this.dp[exten]['priorities'][p];
-                plist[i++] = p;
-            }
-        }
-
-        // Seed the new priority
-        p = prio + 1;
-        tmp[p] = new Array();
-        tmp[p]['application'] = '';
-        tmp[p]['args'] = '';
-        plist[i] = p;
-
-
-        // Empty the original array
-        this.dp[exten]['priorities'] = new Array();
-
-        // Sort the priorities and put them back into the original array
-        plist.sort(this._numCompare);
-        for (i = 0; i < plist.length; i++) {
-            p = Number(plist[i]);
-            this.dp[exten]['priorities'][p] = tmp[p];
-        }
-    }
-
-    this.drawPrioTable(exten);
-    this.highlightPrio(exten, prio);
-    return true;
-}
-
-Dialplan.prototype.insertPrio = function(exten, prio)
-{
-    // Simple wrapper for addPrio()
-    // Create an empty slot.  Subtract one from the new prio because the
-    // behavior of addPrio is to append to the specified location.
-    return this.addPrio(exten, prio - 1);
-}
-
-Dialplan.prototype.delPrio = function(exten, prio)
-{
-    prio = Number(prio);
-    if (this.dp[exten]['priorities'][prio] != 'undefined') {
-        // The .length method on this array always reports number of priorities + 1;
-        // Haven't yet solved this mystery but the below test does work correctly.
-        if (this.dp[exten]['priorities'].length <= 2) {
-            alert('Extensions must have at least one priority');
-            return false;
-        }
-        // Due to javascript's inability to remove an array element while maintaining
-        // associations, we copy the elements into a tmp array and ultimately replace
-        // the object's copy.  We will also have to sort the resulting array manually
-        // so it renders correctly.
-        var tmp = new Array();
-        var plist = new Array();
-        var i = 0;
-        var p;
-
-        for (p in this.dp[exten]['priorities']) {
-            // Notch out the old priority by decrementing all priorities greater
-            // than the requested one.  Try to exclude error handling priorities
-            // which are unrelated to the changed extension.  See README for
-            // more information.
-            // TODO: Make a decision about whether this is the best way to handle
-            // error handling priorities.
-            p = Number(p);
-            if (p > prio && (p < prio + 90 || p > prio + 100)) {
-                tmp[p - 1] = this.dp[exten]['priorities'][p];
-                plist[i++] = p - 1;
-            } else if (p != prio) {
-                tmp[p] = this.dp[exten]['priorities'][p];
-                plist[i++] = p;
-            }
-        }
-
-        // Empty the original array
-        this.dp[exten]['priorities'] = new Array();
-
-        // Sort the priorities and put them back into the original array
-        plist.sort(this._numCompare);
-        for (i = 0; i < plist.length; i++) {
-            p = Number(plist[i]);
-            this.dp[exten]['priorities'][p] = tmp[p];
-        }
-    }
-
-    this.curPrio = 0;
-    this.drawPrioTable(exten);
-    return true;
-}
-
-Dialplan.prototype.renumberPrio = function(newPrio)
-{
-    // Copy the old prio to a temporary location for future use
-    var tmp = new Array();
-    var oldPrio = Number(this.curPrio);
-    tmp = this.dp[this.curExten]['priorities'][oldPrio];
-    newPrio = Number(newPrio);
-
-    // Empty out the old priority
-    this.delPrio(this.curExten, oldPrio);
-
-    this.insertPrio(this.curExten, newPrio);
-    // Copy the old priority into its new home
-    this.dp[this.curExten]['priorities'][newPrio] = tmp;
-    this.drawPrioTable(this.curExten);
-
-    // Highlight the renumbered priority
-    this.curPrio = newPrio;
-    this.highlightPrio(this.curExten, this.curPrio);
-
-    return true;
-}
-
-Dialplan.prototype._numCompare = function(a, b)
-{
-    return (a - b);
-}
\ No newline at end of file
diff --git a/templates/menu.inc b/templates/menu.inc
deleted file mode 100644 (file)
index 070329a..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<a name="top"></a>
-<?php
-$accesskey = $prefs->getValue('widget_accesskey') ?
-    Horde::getAccessKey(_("Select _Context")) : '';
-$menu_view = $prefs->getValue('menu_view');
-?>
-
-<div id="menu">
-
-<?php
-// Only show the context selector if there is more than one available context
-if (count($contexts) > 1) { ?>
-<script language="JavaScript" type="text/javascript">
-<!--
-var loading;
-function contextSubmit(clear)
-{
-
-    if (document.contextMenu.context[document.contextMenu.context.selectedIndex].name != '') {
-        if ((loading == null) || (clear != null)) {
-            loading = true;
-            document.contextMenu.submit();
-        }
-    }
-}
-// -->
-</script>
- <form action="index.php" method="get" name="contextMenu">
-  <span style="float:right">
-   <?php Horde_Util::pformInput() ?>
-   <label for="context" accesskey="<?php echo $accesskey ?>">
-    <select id="context" name="context" onchange="contextSubmit()">
-     <?php
-        foreach ($contexts as $c) {
-            print "<option value=\"$c\"";
-            if ($c == $context) {
-                print " selected";
-            }
-            print ">$c</option>\n";
-        }
-     ?>
-    </select>
-   </label>
-   <?php
-     if (isset($section)) {
-         ?>
-         <input type="hidden" name="section" value="<?php echo $section; ?>" />
-         <?php
-     }
-    ?>
-  </span>
- </form>
-
- <div style="float:right">
-  <?php
-   $link = Horde::link('#', _("Select Context"), '', '', 'contextSubmit(true);
-    return false;');
-   printf('<ul><li>%s%s<br />%s</a></li></ul>',
-    $link, Horde::img('folders/folder_open.png'),
-    ($menu_view != 'icon') ?
-    Horde::highlightAccessKey(_("Select _Context"), $accesskey) : '');
-  ?>
- </div>
-
-<?php } // if (count(contexts) > 1) ?>
-
-  <?php echo Shout::getMenu('string') ?>
-</div>
diff --git a/templates/table-limiter-begin.inc b/templates/table-limiter-begin.inc
deleted file mode 100644 (file)
index 91da037..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<table width="95%" border="0" cellpadding="0" cellspacing="0"><tr><td>
\ No newline at end of file
diff --git a/templates/table-limiter-end.inc b/templates/table-limiter-end.inc
deleted file mode 100644 (file)
index 2c0bb58..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-    </td>
-  </tr>
-</table>
\ No newline at end of file
diff --git a/themes/graphics/add-extension.gif b/themes/graphics/add-extension.gif
deleted file mode 100644 (file)
index c343e4b..0000000
Binary files a/themes/graphics/add-extension.gif and /dev/null differ
diff --git a/themes/graphics/add-user.gif b/themes/graphics/add-user.gif
deleted file mode 100644 (file)
index faac4f1..0000000
Binary files a/themes/graphics/add-user.gif and /dev/null differ
diff --git a/themes/graphics/shout.png b/themes/graphics/shout.png
deleted file mode 100644 (file)
index e819577..0000000
Binary files a/themes/graphics/shout.png and /dev/null differ
diff --git a/themes/graphics/telephone-pole.png b/themes/graphics/telephone-pole.png
deleted file mode 100644 (file)
index 7187c95..0000000
Binary files a/themes/graphics/telephone-pole.png and /dev/null differ
diff --git a/themes/graphics/user.png b/themes/graphics/user.png
deleted file mode 100644 (file)
index 8b2c994..0000000
Binary files a/themes/graphics/user.png and /dev/null differ
diff --git a/themes/screen.css b/themes/screen.css
deleted file mode 100644 (file)
index 057a159..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-.lighthint {
-    color: #fff;
-    font-size: 10px;
-  }
-
-
-.darkhint {
-    color: #700;
-    font-size: 10px;
-  }
-
-table {
-    width: 100%;
-}
-
-.header {
-    color: #fff;
-    background: #a22;
-    text-align: right;
-    font-style: italic;
-    font-weight: bold;
-    font-size: 14px;
-}
-
-.userList{
-    border: 1px solid #000;
-}
-
-.uheader{
-    background: #fff;
-    color: #a22;
-    text-align: left;
-    font-weight: bold;
-    font-size: 130%;
-    border-bottom: 1px dashed #000;
-}
-
-#contextTree {
-    width: 180px;
-    left: 12px;
-    position: fixed;
-    top: 108px;
-    background: #fbb;
-    border: 1px solid #a22;
-    padding: 5px;
-  }
-
-
-#extensionDetail {
-    left: 200px;
-    position: absolute;
-    top: 50px;
-    background: #ccc;
-    border: 1px solid #000;
-    padding: 15px;
-  }
-
-#extensionDetail .lightHint {
-    background: #888;
-    color: #ddd;
-}
-
-#extensionDetail .darkHint{
-     background: #797
-     color: #fff;
-}
-
-#extensionDetail .extension {
-    border: 2px solid #000;
-    background: inherit;
-    padding: 5px;
-    width: 450px;
-}
-
-#extensionDetail .extensionHighlight {
-    border: 2px solid #a22;
-    padding: 5px;
-    padding-bottom: 20px;
-    background: #fbb;
-}
-
-#extensionDetail .extensionBox {
-    background: #fff;
-    color: #a22;
-    font-weight: bold;
-    font-size: 14px;
-    border: 2px solid #787;
-    padding: 5px;
-}
-
-#extensionDetail .extensionBoxHighlight {
-    background: #fbb;
-    color: #000;
-    font-weight: bold;
-    font-size: 14px;
-    border: 2px solid #fff;
-    padding: 5px;
-}
-
-#extensionDetail .pList {
-    border: 1px solid #787;
-    background: inherit;
-    /*left: 10px;
-    position: relative;*/
-}
-
-#extensionDetail .pListHighlight{
-    border: 1px solid #fff;
-    background: #fbb;
-    /*left: 10px;
-    position: relative;*/
-}
-
-#extensionDetail .priority {
-    padding: 4px;
-    /*left: 20px;
-    position: relative;*/
-    background: inherit;
-    /*border: 1px solid #000;*/
-}
-#extensionDetail .priorityHighlight {
-    background: #ccf;
-    padding: 3px;
-    border: 1px solid #000;
-    /*text-decoration: none;
-    font-color: #000;*/
-}
-
-#extensionDetail .pElement {
-    background: inherit;
-    padding: 3px;
-}
-#extensionDetail .pElementHighlight {
-    background: #ccf;
-    padding: 3px;
-    border-bottom: 1px solid #000;
-    border-top: 1px solid #000;
-    text-decoration: none;
-    font-color: #000;
-}
-
-#extensionDetail .pBox {
-    background: #fff;
-    color: #a22;
-    padding: 3px;
-    border: 1px solid #000;
-    width: 120px;
-    height: 16px;
-    text-align: center;
-    vertical-align: middle;
-    text-decoration: underline;
-}
-
-#extensionDetail .add {
-    border: 1px solid #000;
-    font-size: 10px;
-    font-weight: bold;
-    padding: 1px;
-    padding-left: 5px;
-    padding-right: 5px;
-}
-
-#extensionDetail .remove {
-    border: 1px solid #000;
-    font-size: 10px;
-    font-weight: bold;
-    padding: 1px;
-    padding-left: 5px;
-    padding-right: 5px;
-}
-
-#extensionDetail .pButtons {
-    visibility: hidden;
-    padding: 1px;
-}
-
-#extensionDetail .pArgs {
-    border: 1px #777 dotted;
-    padding-left: 5px;
-    padding-right: 5px;
-}
-
-/*#extensionDetail .pButtonsHighlight {
-    visibility: visible;
-    background: #ccf;
-    padding: 3px;
-    border-bottom: 1px solid #000;
-    border-top: 1px solid #000;
-}*/
\ No newline at end of file