+++ /dev/null
-* Convert forms to Beatnik style associative arrays. 2006-01-26 bklang
+++ /dev/null
-<?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 's' then the password check will be skipped. If
-the mailbox is preceded by 'p' then the supplied mailbox is prepended to the
-user'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 'count' digits from the channel'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 'min' ms
-yet less than 'max' ms is followed by silence for at least 'sil' ms then
-the audio playback is aborted and processing jumps to the 'talk' 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 'f' 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 'parkedcalls'
-context.
-
-</usage>
- </application>
- <application name="VoiceMail">
- <synopsis>Leave a voicemail message</synopsis>
- <usage> VoiceMail([s|u|b]extension[@context][&extension[@context]][...]): Leavesvoicemail for a given extension (must be configured in voicemail.conf).
- If the extension is preceded by
-* 's' then instructions for leaving the message will be skipped.
-* 'u' then the "unavailable" message will be played.
- (/var/lib/asterisk/sounds/vm/<exten>/unavail) if it exists.
-* 'b' then the the busy message will be played (that is, busy instead of unavail).
-If the caller presses '0' (zero) during the prompt, the call jumps to
-extension 'o' in the current context.
-If the caller presses '*' during the prompt, the call jumps to
-extension 'a' 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 's' then the password check will be skipped. If
-the mailbox is preceded by 'p' then the supplied mailbox is prepended to the
-user'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 'skip'
-option causes the playback of the message to be skipped if the channel
-is not in the 'up' state (i.e. it hasn't been answered yet. If 'skip' is
-specified, the application will return immediately should the channel not be
-off hook. Otherwise, unless 'noanswer' is specified, the channel channel will
-be answered before the sound is played. Not all channels support playing
-messages while still hook. The 'langoverride' 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([<level>|]<message>)
- 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's positive then we skip the first count1 digits from the
-left. If it'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 => _NXXXXXX,1,SubString,test=2564286161|0|3
-assigns the area code (3 first digits) to variable test.
-exten => _NXXXXXX,1,SubString,test=2564286161|-7|7
-assigns the last 7 digits to variable test.
-exten => _NXXXXXX,1,SubString,test=2564286161|0|-4
-assigns all but the last 4 digits to variable test.
-If there are no parameters it'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's associated extension. For example, the number 555 when suffixed
-with '1212' 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'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 "wav"
- 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't begin recording unless a call is bridged to another channel
-
-Returns -1 if monitor files can'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 '#' 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 'p'), 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:
- 'm' -- set monitor only mode (Listen only, no talking)
- 't' -- set talk only mode. (Talk only, no listening)
- 'p' -- allow user to exit the conference by pressing '#'
- 'X' -- 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.
- 'd' -- dynamically add conference
- 'D' -- dynamically add conference, prompting for a PIN
- 'e' -- select an empty conference
- 'E' -- select an empty pinless conference
- 'v' -- video mode
- 'q' -- quiet mode (don't play enter/leave sounds)
- 'M' -- enable music on hold when the conference has a single caller
- 'x' -- close the conference when last marked user exits
- 'w' -- wait until the marked user enters the conference
- 'b' -- 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)
- 's' -- Present menu (user or admin) when '*' is received ('send' to menu)
- 'a' -- set admin mode
- 'A' -- 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 'user field' 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 'n' 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][&extension[@context]][...]): Leavesvoicemail for a given extension (must be configured in voicemail.conf).
- If the extension is preceded by
-* 's' then instructions for leaving the message will be skipped.
-* 'u' then the "unavailable" message will be played.
- (/var/lib/asterisk/sounds/vm/<exten>/unavail) if it exists.
-* 'b' then the the busy message will be played (that is, busy instead of unavail).
-If the caller presses '0' (zero) during the prompt, the call jumps to
-extension 'o' in the current context.
-If the caller presses '*' during the prompt, the call jumps to
-extension 'a' 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 'i' extension, or if it doesn'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 '#' to select the next channel and use '*' 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 'n' 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's associated extension. For example, the number 1212 when prefixed
-with '555' 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 'TXTCIDNAME'. 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
- 'K' -- Kick all users out of conference
- 'k' -- Kick one user out of conference
- 'L' -- Lock conference
- 'l' -- Unlock conference
- 'M' -- Mute conference
- 'm' -- 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.
-- 'format' is the format of the file type to be recorded (wav, gsm, etc).
-- 'silence' is the number of seconds of silence to allow before returning.
-- 'maxduration' is the maximum recording duration in seconds. If missing
-or 0 there is no maximum.
-- 'option' may be 'skip' to return immediately if the line is not up,
-or 'noanswer' to attempt to record even if the line is not up.
-
-If filename contains '%d', 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 '#' 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 'w' 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 'skip'
-option causes the playback of the message to be skipped if the channel
-is not in the 'up' state (i.e. it hasn't been answered yet. If 'skip' is
-specified, the application will return immediately should the channel not be
-off hook. Otherwise, unless 'noanswer' 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
-'macro-<macroname>', jumping to the 's' 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[&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:
- 't' -- allow the called user transfer the calling user by hitting #.
- 'T' -- allow the calling user to transfer the call by hitting #.
- 'f' -- Forces callerid to be set as the extension of the line
- making/redirecting the outgoing call. For example, some PSTNs
- don't allow callerids from other extensions then the ones
- that are assigned to you.
- 'r' -- indicate ringing to the calling party, pass no audio until answered.
- 'm' -- provide hold music to the calling party until answered.
- 'M(x) -- Executes the macro (x) upon connect of the call
- 'h' -- allow callee to hang up by hitting *.
- 'H' -- allow caller to hang up by hitting *.
- 'C' -- reset call detail record for this call.
- 'P[(x)]' -- privacy mode, using 'x' as database if provided.
- 'g' -- goes on in context if the destination channel hangs up
- 'e' -- Force the caller to Explicitly accept the call
- 'A(x)' -- play an announcement to the called party, using x as file
- 'S(x)' -- hangup the call after x seconds AFTER called party picked up
- 'D([digits])' -- Send DTMF digit string *after* called party has answered
- but before the bridge. (w=500ms sec pause)
- 'L(x[:y][:z])' -- Limit the call to 'x' ms warning when 'y' ms are left
- repeated every 'z' ms) Only 'x' is required, 'y' and 'z' 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 'y' is defined.
- 'timeleft' 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 'parkedcalls'
-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 't' 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(<times>|<weekdays>|<mdays>|<months>?[[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 '*' (for always)
-or as a range. See the 'include' 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 'language'. 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 'fr' and the file 'demo-congrats' is
-requested to be played, if the file 'fr/demo-congrats' exists, then
-it will play that file, and if not will play the normal 'demo-congrats'.
-Always returns 0.
-
-</usage>
- </application>
- <application name="NoCDR">
- <synopsis>Make sure asterisk doesn't save CDR for a certain call</synopsis>
- <usage>NoCDR(): makes sure there won'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 'count' digits from the channel'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'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
+++ /dev/null
-<?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>
-
+++ /dev/null
-<?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
+++ /dev/null
-<?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
+++ /dev/null
-<?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
+++ /dev/null
-<?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
+++ /dev/null
-<?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'));
+++ /dev/null
-<?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&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
+++ /dev/null
-<?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;
- }
- }
-
-}
+++ /dev/null
-<?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;
- }
-
-}
+++ /dev/null
-<?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();
- }
- }
-
-}
+++ /dev/null
-<?php
-class Shout_Exception extends Horde_Exception
-{
-}
\ No newline at end of file
+++ /dev/null
-<?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
+++ /dev/null
-<?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
+++ /dev/null
-<?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;
- }
-}
+++ /dev/null
-<?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;
-}
+++ /dev/null
-<?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
+++ /dev/null
-<?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';
-
-?>
+++ /dev/null
-<?php define('SHOUT_VERSION', '0.1-svn') ?>
\ No newline at end of file
+++ /dev/null
-<?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
+++ /dev/null
-<?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
--- /dev/null
+* Convert forms to Beatnik style associative arrays. 2006-01-26 bklang
--- /dev/null
+<?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 's' then the password check will be skipped. If
+the mailbox is preceded by 'p' then the supplied mailbox is prepended to the
+user'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 'count' digits from the channel'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 'min' ms
+yet less than 'max' ms is followed by silence for at least 'sil' ms then
+the audio playback is aborted and processing jumps to the 'talk' 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 'f' 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 'parkedcalls'
+context.
+
+</usage>
+ </application>
+ <application name="VoiceMail">
+ <synopsis>Leave a voicemail message</synopsis>
+ <usage> VoiceMail([s|u|b]extension[@context][&extension[@context]][...]): Leavesvoicemail for a given extension (must be configured in voicemail.conf).
+ If the extension is preceded by
+* 's' then instructions for leaving the message will be skipped.
+* 'u' then the "unavailable" message will be played.
+ (/var/lib/asterisk/sounds/vm/<exten>/unavail) if it exists.
+* 'b' then the the busy message will be played (that is, busy instead of unavail).
+If the caller presses '0' (zero) during the prompt, the call jumps to
+extension 'o' in the current context.
+If the caller presses '*' during the prompt, the call jumps to
+extension 'a' 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 's' then the password check will be skipped. If
+the mailbox is preceded by 'p' then the supplied mailbox is prepended to the
+user'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 'skip'
+option causes the playback of the message to be skipped if the channel
+is not in the 'up' state (i.e. it hasn't been answered yet. If 'skip' is
+specified, the application will return immediately should the channel not be
+off hook. Otherwise, unless 'noanswer' is specified, the channel channel will
+be answered before the sound is played. Not all channels support playing
+messages while still hook. The 'langoverride' 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([<level>|]<message>)
+ 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's positive then we skip the first count1 digits from the
+left. If it'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 => _NXXXXXX,1,SubString,test=2564286161|0|3
+assigns the area code (3 first digits) to variable test.
+exten => _NXXXXXX,1,SubString,test=2564286161|-7|7
+assigns the last 7 digits to variable test.
+exten => _NXXXXXX,1,SubString,test=2564286161|0|-4
+assigns all but the last 4 digits to variable test.
+If there are no parameters it'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's associated extension. For example, the number 555 when suffixed
+with '1212' 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'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 "wav"
+ 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't begin recording unless a call is bridged to another channel
+
+Returns -1 if monitor files can'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 '#' 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 'p'), 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:
+ 'm' -- set monitor only mode (Listen only, no talking)
+ 't' -- set talk only mode. (Talk only, no listening)
+ 'p' -- allow user to exit the conference by pressing '#'
+ 'X' -- 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.
+ 'd' -- dynamically add conference
+ 'D' -- dynamically add conference, prompting for a PIN
+ 'e' -- select an empty conference
+ 'E' -- select an empty pinless conference
+ 'v' -- video mode
+ 'q' -- quiet mode (don't play enter/leave sounds)
+ 'M' -- enable music on hold when the conference has a single caller
+ 'x' -- close the conference when last marked user exits
+ 'w' -- wait until the marked user enters the conference
+ 'b' -- 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)
+ 's' -- Present menu (user or admin) when '*' is received ('send' to menu)
+ 'a' -- set admin mode
+ 'A' -- 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 'user field' 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 'n' 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][&extension[@context]][...]): Leavesvoicemail for a given extension (must be configured in voicemail.conf).
+ If the extension is preceded by
+* 's' then instructions for leaving the message will be skipped.
+* 'u' then the "unavailable" message will be played.
+ (/var/lib/asterisk/sounds/vm/<exten>/unavail) if it exists.
+* 'b' then the the busy message will be played (that is, busy instead of unavail).
+If the caller presses '0' (zero) during the prompt, the call jumps to
+extension 'o' in the current context.
+If the caller presses '*' during the prompt, the call jumps to
+extension 'a' 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 'i' extension, or if it doesn'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 '#' to select the next channel and use '*' 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 'n' 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's associated extension. For example, the number 1212 when prefixed
+with '555' 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 'TXTCIDNAME'. 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
+ 'K' -- Kick all users out of conference
+ 'k' -- Kick one user out of conference
+ 'L' -- Lock conference
+ 'l' -- Unlock conference
+ 'M' -- Mute conference
+ 'm' -- 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.
+- 'format' is the format of the file type to be recorded (wav, gsm, etc).
+- 'silence' is the number of seconds of silence to allow before returning.
+- 'maxduration' is the maximum recording duration in seconds. If missing
+or 0 there is no maximum.
+- 'option' may be 'skip' to return immediately if the line is not up,
+or 'noanswer' to attempt to record even if the line is not up.
+
+If filename contains '%d', 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 '#' 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 'w' 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 'skip'
+option causes the playback of the message to be skipped if the channel
+is not in the 'up' state (i.e. it hasn't been answered yet. If 'skip' is
+specified, the application will return immediately should the channel not be
+off hook. Otherwise, unless 'noanswer' 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
+'macro-<macroname>', jumping to the 's' 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[&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:
+ 't' -- allow the called user transfer the calling user by hitting #.
+ 'T' -- allow the calling user to transfer the call by hitting #.
+ 'f' -- Forces callerid to be set as the extension of the line
+ making/redirecting the outgoing call. For example, some PSTNs
+ don't allow callerids from other extensions then the ones
+ that are assigned to you.
+ 'r' -- indicate ringing to the calling party, pass no audio until answered.
+ 'm' -- provide hold music to the calling party until answered.
+ 'M(x) -- Executes the macro (x) upon connect of the call
+ 'h' -- allow callee to hang up by hitting *.
+ 'H' -- allow caller to hang up by hitting *.
+ 'C' -- reset call detail record for this call.
+ 'P[(x)]' -- privacy mode, using 'x' as database if provided.
+ 'g' -- goes on in context if the destination channel hangs up
+ 'e' -- Force the caller to Explicitly accept the call
+ 'A(x)' -- play an announcement to the called party, using x as file
+ 'S(x)' -- hangup the call after x seconds AFTER called party picked up
+ 'D([digits])' -- Send DTMF digit string *after* called party has answered
+ but before the bridge. (w=500ms sec pause)
+ 'L(x[:y][:z])' -- Limit the call to 'x' ms warning when 'y' ms are left
+ repeated every 'z' ms) Only 'x' is required, 'y' and 'z' 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 'y' is defined.
+ 'timeleft' 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 'parkedcalls'
+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 't' 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(<times>|<weekdays>|<mdays>|<months>?[[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 '*' (for always)
+or as a range. See the 'include' 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 'language'. 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 'fr' and the file 'demo-congrats' is
+requested to be played, if the file 'fr/demo-congrats' exists, then
+it will play that file, and if not will play the normal 'demo-congrats'.
+Always returns 0.
+
+</usage>
+ </application>
+ <application name="NoCDR">
+ <synopsis>Make sure asterisk doesn't save CDR for a certain call</synopsis>
+ <usage>NoCDR(): makes sure there won'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 'count' digits from the channel'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'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
--- /dev/null
+<?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>
+
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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'));
--- /dev/null
+<?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&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
--- /dev/null
+<?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;
+ }
+ }
+
+}
--- /dev/null
+<?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;
+ }
+
+}
--- /dev/null
+<?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();
+ }
+ }
+
+}
--- /dev/null
+<?php
+class Shout_Exception extends Horde_Exception
+{
+}
\ No newline at end of file
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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;
+}
--- /dev/null
+<?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
--- /dev/null
+<?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';
+
+?>
--- /dev/null
+<?php define('SHOUT_VERSION', '0.1-svn') ?>
\ No newline at end of file
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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 .
+'"' ?>>
--- /dev/null
+<?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
--- /dev/null
+<?php
+echo Horde::link(
+ Util::addParameter(
+ Horde::applicationUrl("edit/context.php"), "context", $context));
+echo $context;
+echo "</a><br />";
\ No newline at end of file
--- /dev/null
+<?php
+$RENDERER->beginActive($Form->getTitle());
+$RENDERER->renderFormActive($Form, $vars);
+$RENDERER->submit();
+$RENDERER->end();
+$Form->close($RENDERER);
\ No newline at end of file
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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"> </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");
+ ?> <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"> </td></tr>
+ <?php
+ }
+ }
+ ?>
+ </table>
+ </td></tr>
+</>
\ No newline at end of file
--- /dev/null
+ // -->
+ </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
--- /dev/null
+<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
--- /dev/null
+<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
--- /dev/null
+ </table>
+</div>
\ No newline at end of file
--- /dev/null
+<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
--- /dev/null
+<?php
+$RENDERER->beginActive($Form->getTitle());
+$RENDERER->renderFormActive($Form, $vars);
+$RENDERER->submit();
+$RENDERER->end();
+$Form->close($RENDERER);
\ No newline at end of file
--- /dev/null
+<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>
--- /dev/null
+/**
+ * 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
--- /dev/null
+<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>
--- /dev/null
+<table width="95%" border="0" cellpadding="0" cellspacing="0"><tr><td>
\ No newline at end of file
--- /dev/null
+ </td>
+ </tr>
+</table>
\ No newline at end of file
--- /dev/null
+.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
+++ /dev/null
-<?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 .
-'"' ?>>
+++ /dev/null
-<?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
+++ /dev/null
-<?php
-echo Horde::link(
- Util::addParameter(
- Horde::applicationUrl("edit/context.php"), "context", $context));
-echo $context;
-echo "</a><br />";
\ No newline at end of file
+++ /dev/null
-<?php
-$RENDERER->beginActive($Form->getTitle());
-$RENDERER->renderFormActive($Form, $vars);
-$RENDERER->submit();
-$RENDERER->end();
-$Form->close($RENDERER);
\ No newline at end of file
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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"> </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");
- ?> <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"> </td></tr>
- <?php
- }
- }
- ?>
- </table>
- </td></tr>
-</>
\ No newline at end of file
+++ /dev/null
- // -->
- </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
+++ /dev/null
-<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
+++ /dev/null
-<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
+++ /dev/null
- </table>
-</div>
\ No newline at end of file
+++ /dev/null
-<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
+++ /dev/null
-<?php
-$RENDERER->beginActive($Form->getTitle());
-$RENDERER->renderFormActive($Form, $vars);
-$RENDERER->submit();
-$RENDERER->end();
-$Form->close($RENDERER);
\ No newline at end of file
+++ /dev/null
-<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>
+++ /dev/null
-/**
- * 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
+++ /dev/null
-<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>
+++ /dev/null
-<table width="95%" border="0" cellpadding="0" cellspacing="0"><tr><td>
\ No newline at end of file
+++ /dev/null
- </td>
- </tr>
-</table>
\ No newline at end of file
+++ /dev/null
-.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