From: Ben Klang Date: Wed, 30 Dec 2009 23:02:27 +0000 (-0500) Subject: Move files into app subdir X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=c117e4af33243766dc68c0d1668f8385e37aea0b;p=horde.git Move files into app subdir --- diff --git a/TODO b/TODO deleted file mode 100644 index c53e828b7..000000000 --- a/TODO +++ /dev/null @@ -1 +0,0 @@ -* Convert forms to Beatnik style associative arrays. 2006-01-26 bklang diff --git a/config/applist.xml.dist b/config/applist.xml.dist deleted file mode 100644 index 07f72e4e1..000000000 --- a/config/applist.xml.dist +++ /dev/null @@ -1,862 +0,0 @@ - - - Say Alpha - SayAlpha(string): Spells the passed string - - - - - Send a FAX file - 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. - - - - - Enter voicemail system - 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. - - - - - Strip Least Significant Digits - 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. - - - - - Change monitoring filename of a channel - 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. - - - - - Background a file with talk detect - 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. - - - - - Say Number - SayNumber(digits[,gender]): Says the passed number. SayNumber is using -the current language setting for the channel. (See app SetLanguage). - - - - - Provide directory of voicemail extensions - 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. - - - - - Set CallerID - 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 - - - - - Indicate congestion and stop - 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. - - - - Indicate busy condition and stop - 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. - - - - Park yourself - 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. - - - - - Leave a voicemail message - 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. - - - - - Waits for some time - 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) - - - - - Change the presentation for the callerid - Callingpres(number): Changes the presentation for the callerid. Should be called before placing an outgoing call - - - - - Play Music On Hold indefinitely - 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. - - - - - Set absolute maximum time of call - AbsoluteTimeout(seconds): Set the absolute maximum amount of time permitted -for a call. A setting of 0 disables the timeout. Always returns 0. - - - - - Enter voicemail system - 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. - - - - - Play a file while awaiting extension - 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. - - - - - Send arbitrary text to verbose output - Verbose([<level>|]<message>) - level must be an integer value. If not specified, defaults to 0. Always returns 0. - - - - - Stop monitoring a channel - StopMonitor -Stops monitoring a channel. Has no effect if the channel is not monitored - - - - - (Deprecated) Save substring digits in a given variable - (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 - - - - - MeetMe participant count - 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. - - - - - Set CallerID Name - 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 - - - - - Flashes a Zap Trunk - 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 - - - - - Append trailing digits - 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. - - - - - Sets account code - SetAccount([account]): Set the channel account code for billing -purposes. Always returns 0. - - - - - Say Phonetic - SayPhonetic(string): Spells the passed string with phonetic alphabet - - - - - Monitor a channel - 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. - - - - - Wait, playing Music On Hold - 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. - - - - - Answer a channel if ringing - Answer(): If the channel is ringing, answer it, otherwise do nothing. -Returns 0 unless it tries to answer the channel and fails. - - - - - Echo audio read back to the user - 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. - - - - - Say Digits - SayDigits(digits): Says the passed digits. SayDigits is using the -current language setting for the channel. (See app setLanguage) - - - - - Check if vmbox exists - MailboxExists(mailbox[@context]): Conditionally branches to priority n+101 -if the specified voice mailbox exists. - - - - - Goto a particular priority, extension, or context - 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. - - - - - MeetMe conference bridge - 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 - - - - - Set global variable to value - SetGlobalVar(#n=value): Sets global variable n to value. Global -variable are available across channels. - - - - - Soft Hangup Application - SoftHangup(Technology/resource) -Hangs up the requested channel. Always returns 0 - - - - - Set the CDR user field - [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 - - - - - Indicate ringing tone - Ringing(): Request that the channel indicate ringing tone to the user. -Always returns 0. - - - - - Execute a system command - 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. - - - - - Leave a voicemail message - 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. - - - - - Set maximum timeout between digits - 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. - - - - - Scan Zap channels to monitor calls - ZapScan allows a call center manager to monitor Zap channels in -a convenient way. Use '#' to select the next channel and use '*' to exit - - - - - Try executing a system command - 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. - - - - - Sets AMA Flags - SetAMAFlags([flag]): Set the channel AMA Flags for billing -purposes. Always returns 0. - - - - - Wait for Ring Application - 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 - - - - - Prepend leading digits - 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. - - - - - Append to the CDR user field - [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 - - - - - Lookup caller name from TXT record - 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. - - - - - Receive a FAX to a file - 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. - - - - - Transfer caller to remote extension - 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. - - - - - Set default Music On Hold class - 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. - - - - - MeetMe conference Administration - 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 - - - - - Set variable to value - Setvar(#n=value): Sets channel specific variable n to value - - - - Record to a file - 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. - - - - - Set CallerID Number - 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 - - - - - Resets the Call Data Record - 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. - - - - - Play a file - 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. - - - - - Macro Implementation - 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. - - - - - Change the dtmfmode for a SIP call - SIPDtmfMode(inband|info|rfc2833): Changes the dtmfmode for a SIP call - - - - - Waits for some time - 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) - - - - - Place a call and connect to the current channel - 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 - - - - - Answer a parked call - 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. - - - - - Set maximum timeout awaiting response - 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. - - - - - Unconditional hangup - Hangup(): Unconditionally hangs up a given channel by returning -1 always. - - - - - Conditional goto on current time - 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. - - - - No operation - NoOp(): No-operation; Does nothing. - - - - Set CallerID Presentation - 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 - - - - - - Sets user language - 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. - - - - - Make sure asterisk doesn't save CDR for a certain call - NoCDR(): makes sure there won't be any CDR written for a certain call - - - - Strip leading digits - 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. - - - - - Indicate progress - Progress(): Request that the channel indicate in-band progress is -available to the user. -Always returns 0. - - - - - Barge in (monitor) Zap channel - 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. - - - - Conditional goto - 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. - - - \ No newline at end of file diff --git a/config/conf.xml b/config/conf.xml deleted file mode 100644 index 317511531..000000000 --- a/config/conf.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - Context Storage - Sql - - - - contexts - - - - - - - - Extension Storage - Ldap - - - - - - - - - - - - - - - - - Device Storage - Ldap - - - - - - - - - sip_peers - - - - - - - diff --git a/devices.php b/devices.php deleted file mode 100644 index cac6319b0..000000000 --- a/devices.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - */ -@define('SHOUT_BASE', dirname(__FILE__)); -require_once SHOUT_BASE . '/lib/base.php'; -require_once SHOUT_BASE . '/lib/Forms/DeviceForm.php'; - -$action = Horde_Util::getFormData('action'); -$devices = $shout_devices->getDevices($context); -$devid = Horde_Util::getFormData('devid'); -$vars = Horde_Variables::getDefaultVariables(); - -//$tabs = Shout::getTabs($context, $vars); - -$RENDERER = new Horde_Form_Renderer(); - -$section = 'devices'; -$title = _("Devices: "); - -switch ($action) { - case 'save': - $Form = new DeviceDetailsForm($vars); - - // Show the list if the save was successful, otherwise back to edit. - if ($Form->isSubmitted() && $Form->isValid()) { - try { - $shout_devices->saveDevice($Form->getVars()); - $notification->push(_("Device settings saved.")); - } catch (Exception $e) { - $notification->push($e); - } - $action = 'list'; - break; - } else { - $action = 'edit'; - } - case 'add': - case 'edit': - if ($action == 'add') { - $title .= _("New Device"); - // Treat adds just like an empty edit - $action = 'edit'; - } else { - $title .= sprintf(_("Edit Device %s"), $extension); - - } - - $FormName = 'DeviceDetailsForm'; - $vars = new Horde_Variables($devices[$devid]); - $Form = new DeviceDetailsForm($vars); - - $Form->open($RENDERER, $vars, Horde::applicationUrl('devices.php'), 'post'); - - break; - - - case 'delete': - $notification->push("Not supported."); - break; - - case 'list': - default: - $action = 'list'; - $title .= _("List Users"); -} - -require SHOUT_TEMPLATES . '/common-header.inc'; -require SHOUT_TEMPLATES . '/menu.inc'; - -$notification->notify(); - -//echo $tabs->render($section); - -require SHOUT_TEMPLATES . '/devices/' . $action . '.inc'; - -require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/dialplan.php b/dialplan.php deleted file mode 100644 index 883d2c1cd..000000000 --- a/dialplan.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - * - * @package shout - */ - -if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) { - header('Location: /'); - exit(); -} - -require_once SHOUT_BASE . '/lib/Dialplan.php'; -$dialplan = &$shout->getDialplan($context); - -// Set up the tree. -$dpgui = Shout_Dialplan::singleton('x', $dialplan); - -//$action = Horde_Util::getFormData("action"); -// $action = 'manager'; - -$title = _("Dialplan Manager"); - -require SHOUT_TEMPLATES . '/common-header.inc'; -require SHOUT_TEMPLATES . '/menu.inc'; - -$notification->notify(); - -echo $tabs->render($section); - -// require SHOUT_BASE . "/dialplan/$action.php"; - -require SHOUT_TEMPLATES . '/dialplan/manager.inc'; - -// Horde::addScriptFile('httpclient.js', 'horde', true); -// Horde::addScriptFile('hideable.js', 'horde', true); -// require HORDE_TEMPLATES . '/common-header.inc'; -// require HORDE_TEMPLATES . '/portal/sidebar.inc'; - - -// require SHOUT_TEMPLATES . "/dialplan/dialplanlist.inc"; - - - - - - - - - - -require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/dialplan/edit.php b/dialplan/edit.php deleted file mode 100644 index 18ef2b4c9..000000000 --- a/dialplan/edit.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * See the enclosed file LICENSE for license information (GPL). If you - * did not receive this file, see http://www.horde.org/licenses/gpl.php. - */ -@define('SHOUT_BASE', dirname(__FILE__) . '/..'); -require_once SHOUT_BASE . '/lib/Dialplan.php'; -require_once 'Horde/Variables.php'; - -$RENDERER = &new Horde_Form_Renderer(); - -$empty = ''; - -$vars = &Variables::getDefaultVariables($empty); -$formname = $vars->get('formname'); -$context = $vars->get('context'); -$extension = $vars->get('extension'); -$dialplan = &$shout->getDialplan($context); - -$ExtensionDetailsForm = &Horde_Form::singleton('ExtensionDetailsForm', $vars); -$ExtensionDetailsFormValid = $ExtensionDetailsForm->validate($vars, true); - -$ExtensionDetailsForm->open($RENDERER, $vars, 'dialplan.php', 'post'); -$ExtensionDetailsForm->preserveVarByPost($vars, "section"); -$ExtensionDetailsForm->preserve($vars); -require SHOUT_TEMPLATES . '/table-limiter-begin.inc'; -$RENDERER->beginActive($ExtensionDetailsForm->getTitle()); -$RENDERER->renderFormActive($ExtensionDetailsForm, $vars); -# FIXME Maybe this should be a subclass inheriting from the From/Renderer object -# instead of a simple include? -$i = 0; -require SHOUT_TEMPLATES . '/dialplan/priority-form-begin.inc'; -foreach ($dialplan['extensions'][$extension] as $priority => $application) { - require SHOUT_TEMPLATES . '/dialplan/priority-form-line.inc'; - $i++; -} -require SHOUT_TEMPLATES . '/dialplan/priority-form-end.inc'; -$RENDERER->submit('Add Priority'); -$RENDERER->submit('Add 5 Priorities'); -$RENDERER->submit('Save'); -$RENDERER->end(); -$ExtensionDetailsForm->close($RENDERER); -require SHOUT_TEMPLATES . '/table-limiter-end.inc'; \ No newline at end of file diff --git a/extensions.php b/extensions.php deleted file mode 100644 index 295d1658a..000000000 --- a/extensions.php +++ /dev/null @@ -1,99 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - */ -@define('SHOUT_BASE', dirname(__FILE__)); -require_once SHOUT_BASE . '/lib/base.php'; -require_once SHOUT_BASE . '/lib/Forms/ExtensionForm.php'; -//require_once SHOUT_BASE . '/lib/Shout.php'; - -$action = Horde_Util::getFormData('action'); -$extension = Horde_Util::getFormData('extension'); -$extensions = $shout_extensions->getExtensions($context); - -$vars = Horde_Variables::getDefaultVariables(); - -//$tabs = Shout::getTabs($context, $vars); - -$RENDERER = new Horde_Form_Renderer(); - -$section = 'extensions'; -$title = _("Extensions: "); - -switch ($action) { -case 'save': - $title .= sprintf(_("Save Extension %s"), $extension); - $FormName = $vars->get('formname'); - - $Form = &Horde_Form::singleton($FormName, $vars); - - $FormValid = $Form->validate($vars, true); - - if ($Form->isSubmitted() && $FormValid) { - // Form is Valid and Submitted - try { - $Form->execute(); - } catch (Exception $e) { - $notification->push($e); - } - $notification->push(_("User information updated."), - 'horde.success'); - break; - } else { - $action = 'edit'; - // Fall-through to the "edit" action - } - -case 'add': -case 'edit': - if ($action == 'add') { - $title .= _("New Extension"); - // Treat adds just like an empty edit - $action = 'edit'; - } else { - $title .= sprintf(_("Edit Extension %s"), $extension); - - } - - $FormName = 'ExtensionDetailsForm'; - $vars = new Horde_Variables($extensions[$extension]); - $Form = &Horde_Form::singleton($FormName, $vars); - - $Form->open($RENDERER, $vars, Horde::applicationUrl('extensions.php'), 'post'); - - break; - -case 'delete': - $title .= sprintf(_("Delete Extension %s"), $extension); - $extension = Horde_Util::getFormData('extension'); - - $res = $shout->deleteUser($context, $extension); - - if (!$res) { - echo "Failed!"; - print_r($res); - } - $notification->push("User Deleted."); - break; - -case 'list': -default: - $action = 'list'; - $title .= _("List Users"); -} - -require SHOUT_TEMPLATES . '/common-header.inc'; -require SHOUT_TEMPLATES . '/menu.inc'; - -$notification->notify(); - -//echo $tabs->render($section); - -require SHOUT_TEMPLATES . '/extensions/' . $action . '.inc'; - -require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/index.php b/index.php deleted file mode 100644 index 4097661e1..000000000 --- a/index.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - * - * @package shout - */ - -@define('SHOUT_BASE', dirname(__FILE__)); -$shout_configured = (is_readable(SHOUT_BASE . '/config/conf.php')); - -if (!$shout_configured) { -require SHOUT_BASE . '/../lib/Test.php'; - Horde_Test::configFilesMissing('Shout', SHOUT_BASE, - array('conf.php')); -} - -require_once SHOUT_BASE . '/lib/base.php'; -header('Location: ' . Horde::applicationUrl('extensions.php')); diff --git a/lib/Dialplan.php b/lib/Dialplan.php deleted file mode 100644 index 0549b3294..000000000 --- a/lib/Dialplan.php +++ /dev/null @@ -1,227 +0,0 @@ - - * - * 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 - * - * 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 - * @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 '
'."\n"; - $this->_tree->renderTree(true); - print '
'."\n"; - print ' Back to Top'."\n"; - print '
'."\n"; - return true; - } - - function generateAppList() - { - $applist = Shout::getApplist(); - print ''."\n"; - return true; - } - - function renderExtensions() - { - if(!isset($this->_dialplan['extensions'])) { - print '
'."\n"; - print '
No Configured Extensions
'."\n"; - print '
'."\n"; - } else { - print ''."\n"; - print ''."\n"; - - print '
'."\n"; - print '
'."\n"; - $e = 0; - foreach($this->_dialplan['extensions'] as $extension => $priorities) { - print '
'; - print ''."\n"; - print '
'."\n"; - print '
'."\n"; - $e++; - print '
'."\n"; - print '
'."\n"; - print ''."\n"; - } - print '
'."\n"; - print '
'."\n"; - } - } -} \ No newline at end of file diff --git a/lib/Driver.php b/lib/Driver.php deleted file mode 100644 index 421afd3e2..000000000 --- a/lib/Driver.php +++ /dev/null @@ -1,160 +0,0 @@ - - * - * 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 - * @version $Revision: 76 $ - * @since Shout 0.1 - * @package Shout - */ - -class Shout_Driver { - - /** - * Hash containing connection parameters. - * - * @var array $_params - */ - var $_params = array(); - - function Shout_Driver($params = array()) - { - $this->_params = $params; - } - - /** - * Get a list of contexts from the instantiated driver and filter - * the returned contexts for those which the current user can see/edit - * - * @param optional string $filter Filter for types of contexts to return. - * One of "system" "customer" or "all" - * - * @param optional string $filterperms Filter contexts for given permissions - * - * @return array Contexts valid for this user - * - * @access public - */ - function getContexts($filters = "all", $filterperms = null) - { - throw new Shout_Exception("This function is not implemented."); - } - - /** - * For the given context and type, make sure the context has the - * appropriate properties, that it is effectively of that "type" - * - * @param string $context the context to check type for - * - * @param string $type the type to verify the context is of - * - * @return boolean true of the context is of type, false if not - * - * @access public - */ - function checkContextType($context, $type) - { - throw new Shout_Exception("This function is not implemented."); - } - - /** - * Get a list of users valid for the current context. Return an array - * indexed by the extension. - * - * @param string $context Context for which users should be returned - * - * @return array User information indexed by voice mailbox number - */ - function getUsers($context) - { - throw new Shout_Exception("This function is not implemented."); - } - - /** - * Returns the name of the user's default context - * - * @return string User's default context - */ - function getHomeContext() - { - throw new Shout_Exception("This function is not implemented."); - } - - /** - * Get a context's properties - * - * @param string $context Context to get properties for - * - * @return integer Bitfield of properties valid for this context - */ - function getContextProperties($context) - { - throw new Shout_Exception("This function is not implemented."); - } - - /** - * Get a context's extensions and return as a multi-dimensional associative - * array - * - * @param string $context Context to return extensions for - * - * @return array Multi-dimensional associative array of extensions data - * - */ - function getDialplan($context) - { - throw new Shout_Exception("This function is not implemented."); - } - - /** - * Attempts to return a concrete Shout_Driver instance based on - * $driver. - * - * @param string $driver The type of the concrete Shout_Driver subclass - * to return. The class name is based on the storage - * driver ($driver). The code is dynamically - * included. - * - * @param array $params (optional) A hash containing any additional - * configuration or connection parameters a - * subclass might need. - * - * @return mixed The newly created concrete Shout_Driver instance, or - * false on an error. - */ - function &factory($class, $driver = null, $params = null) - { - if (is_null($driver)) { - $driver = $GLOBALS['conf'][$class]['driver']; - } - - $driver = basename($driver); - - if (is_null($params)) { - if ($GLOBALS['conf'][$class]['params']['driverconfig'] == 'horde') { - $params = array_merge(Horde::getDriverConfig('storage', $driver), - $GLOBALS['conf'][$class]['params']); - } else { - $params = $GLOBALS['conf'][$class]['params']; - } - } - - $params['class'] = $class; - - require_once dirname(__FILE__) . '/Driver/' . $driver . '.php'; - $class = 'Shout_Driver_' . $driver; - if (class_exists($class)) { - return new $class($params); - } else { - return false; - } - } - -} diff --git a/lib/Driver/Ldap.php b/lib/Driver/Ldap.php deleted file mode 100644 index ee0358fde..000000000 --- a/lib/Driver/Ldap.php +++ /dev/null @@ -1,776 +0,0 @@ -_connect(); - - /* These next lines will translate between indexes used in the - * application and LDAP. The rationale is that translation here will - * help make Congregation more driver-independant. The keys used to - * contruct user arrays should be more appropriate to human-legibility - * (name instead of 'cn' and email instead of 'mail'). This translation - * is only needed because LDAP indexes users based on an arbitrary - * attribute and the application indexes by extension/context. In my - * environment users are indexed by their 'mail' attribute and others - * may index based on 'cn' or 'uid'. Any time a new $prefs['uid'] needs - * to be supported, this function should be checked and possibly - * extended to handle that translation. - */ - switch($this->_params['uid']) { - case 'cn': - $this->_ldapKey = 'cn'; - $this->_appKey = 'name'; - break; - case 'mail': - $this->_ldapKey = 'mail'; - $this->_appKey = 'email'; - break; - case 'uid': - # FIXME Probably a better app key to map here - # There is no value that maps uid to LDAP so we can choose to use - # either extension or name, or anything really. I want to - # support it since it's a very common DN attribute. - # Since it's entirely administrator's preference, I'll - # set it to name for now - $this->_ldapKey = 'uid'; - $this->_appKey = 'name'; - break; - case 'voiceMailbox': - $this->_ldapKey = 'voiceMailbox'; - $this->_appKey = 'extension'; - break; - } - } - - /** - * Get a list of users valid for the contexts - * - * @param string $context Context in which to search - * - * @return array User information indexed by voice mailbox number - */ - public function getExtensions($context) - { - - static $entries = array(); - if (isset($entries[$context])) { - return $entries[$context]; - } - - $this->_params['basedn']; - - $filter = '(&'; - $filter .= '(objectClass=AsteriskVoiceMail)'; - $filter .= '(AstContext='.$context.')'; - $filter .= ')'; - - $attributes = array( - 'cn', - 'mail', - 'AstVoicemailMailbox', - 'AstVoicemailPassword', - 'AstVoicemailOptions', - 'AstVoicemailPager', - 'telephoneNumber', - 'AstExtension' - ); - - $search = ldap_search($this->_LDAP, $this->_params['basedn'], $filter, $attributes); - if ($search === false) { - throw new Shout_Exception("Unable to search directory: " . - ldap_error($this->_LDAP), ldap_errno($this->_LDAP)); - } - - $res = ldap_get_entries($this->_LDAP, $search); - if ($res === false) { - throw new Shout_Exception("Unable to fetch results from directory: " . - ldap_error($this->_LDAP), ldap_errno($this->_LDAP)); - } - - // ATTRIBUTES RETURNED FROM ldap_get_entries ARE ALL LOWER CASE!! - // It's a PHP thing. - $entries[$context] = array(); - $i = 0; - while ($i < $res['count']) { - list($extension) = explode('@', $res[$i]['astvoicemailmailbox'][0]); - $entries[$context][$extension] = array('extension' => $extension); - - $j = 0; - $entries[$context][$extension]['mailboxopts'] = array(); - if (empty($res[$i]['astvoicemailoptions']['count'])) { - $res[$i]['astvoicemailoptions']['count'] = -1; - } - while ($j < $res[$i]['astvoicemailoptions']['count']) { - $entries[$context][$extension]['mailboxopts'][] = - $res[$i]['astvoicemailoptions'][$j]; - $j++; - } - - $entries[$context][$extension]['mailboxpin'] = - $res[$i]['astvoicemailpassword'][0]; - - $entries[$context][$extension]['name'] = - $res[$i]['cn'][0]; - - $entries[$context][$extension]['email'] = - $res[$i]['mail'][0]; - - $entries[$context][$extension]['pageremail'] = - $res[$i]['astvoicemailpager'][0]; - - $j = 0; - $entries[$context][$extension]['numbers'] = array(); - if (empty($res[$i]['telephonenumber']['count'])) { - $res[$i]['telephonenumber']['count'] = -1; - } - while ($j < $res[$i]['telephonenumber']['count']) { - $entries[$context][$extension]['numbers'][] = - $res[$i]['telephonenumber'][$j]; - $j++; - } - - $j = 0; - $entries[$context][$extension]['devices'] = array(); - if (empty($res[$i]['astextension']['count'])) { - $res[$i]['astextension']['count'] = -1; - } - while ($j < $res[$i]['astextension']['count']) { - $entries[$context][$extension]['devices'][] = - $res[$i]['astextension'][$j]; - $j++; - } - - - $i++; - - } - - ksort($entries[$context]); - - return($entries[$context]); - } - - /** - * Get a list of destinations valid for this extension. - * A destination is either a telephone number, a VoIP device or an - * Instant Messaging address (a special case of VoIP). - * - * @param string $context Context for the extension - * @param string $extension Extension for which to return destinations - */ - function getDestinations($context, $extension) - { - // FIXME: LDAP filter injection - $filter = '(&(AstContext=%s)(AstVoicemailMailbox=%s))'; - $filter = sprintf($filter, $context, $extension); - - $attrs = array('telephoneNumber', 'AstExtensions'); - - $res = ldap_search($this->_LDAP, $this->_params['basedn'], - $filter, $attrs); - - if ($res === false) { - $msg = sprintf('Error while searching LDAP. Code %s; Message "%s"', - ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); - Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); - throw new Shout_Exception(_("Internal error searching the directory.")); - } - - $res = ldap_get_entries($this->_LDAP, $res); - - if ($res === false) { - $msg = sprintf('Error while searching LDAP. Code %s; Message "%s"', - ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); - Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); - throw new Shout_Exception(_("Internal error searching the directory.")); - } - - if ($res['count'] != 1) { - $msg = sprintf('Error while searching LDAP. Code %s; Message "%s"', - ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); - Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); - throw new Shout_Exception(_("Wrong number of entries found for this search.")); - } - - return array('numbers' => $res['telephonenumbers'], - 'devices' => $res['astextensions']); - } - - /** - * Get a context's dialplan and return as a multi-dimensional associative - * array - * - * @param string $context Context to return extensions for - * - * @param boolean $preprocess Parse includes and barelines and add their - * information into the extensions array - * - * @return array Multi-dimensional associative array of extensions data - * - */ - public function getDialplan($context, $preprocess = false) - { - # FIXME Implement preprocess functionality. Don't forget to cache! - static $dialplans = array(); - if (isset($dialplans[$context])) { - return $dialplans[$context]; - } - - $res = @ldap_search($this->_LDAP, - SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'], - "(&(objectClass=".SHOUT_CONTEXT_EXTENSIONS_OBJECTCLASS.")(context=$context))", - array(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE, SHOUT_DIALPLAN_INCLUDE_ATTRIBUTE, - SHOUT_DIALPLAN_IGNOREPAT_ATTRIBUTE, 'description', - SHOUT_DIALPLAN_BARELINE_ATTRIBUTE)); - if (!$res) { - return PEAR::raiseError("Unable to locate any extensions " . - "underneath ".SHOUT_ASTERISK_BRANCH.",".$this->_params['basedn'] . - " matching those search filters"); - } - - $res = ldap_get_entries($this->_LDAP, $res); - $dialplans[$context] = array(); - $dialplans[$context]['name'] = $context; - $i = 0; - while ($i < $res['count']) { - # Handle extension lines - if (isset($res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)])) { - $j = 0; - while ($j < $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)]['count']) { - @$line = $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)][$j]; - - # Basic sanity check for length. FIXME - if (strlen($line) < 5) { - break; - } - # Can't use strtok here because there may be commass in the - # arg string - - # Get the extension - $token1 = strpos($line, ','); - $token2 = strpos($line, ',', $token1 + 1); - $token3 = strpos($line, '(', $token2 + 1); - - $extension = substr($line, 0, $token1); - if (!isset($dialplans[$context]['extensions'][$extension])) { - $dialplan[$context]['extensions'][$extension] = array(); - } - $token1++; - # Get the priority - $priority = substr($line, $token1, $token2 - $token1); - $dialplans[$context]['extensions'][$extension][$priority] = - array(); - $token2++; - - # Get Application and args - $application = substr($line, $token2, $token3 - $token2); - - if ($token3) { - $application = substr($line, $token2, $token3 - $token2); - $args = substr($line, $token3); - $args = preg_replace('/^\(/', '', $args); - $args = preg_replace('/\)$/', '', $args); - } else { - # This application must not have any args - $application = substr($line, $token2); - $args = ''; - } - - # Merge all that data into the returning array - $dialplans[$context]['extensions'][$extension][$priority]['application'] = - $application; - $dialplans[$context]['extensions'][$extension][$priority]['args'] = - $args; - $j++; - } - - # Sort the extensions data - foreach ($dialplans[$context]['extensions'] as - $extension => $data) { - ksort($dialplans[$context]['extensions'][$extension]); - } - uksort($dialplans[$context]['extensions'], - array(new Shout, "extensort")); - } - # Handle include lines - if (isset($res[$i]['asteriskincludeline'])) { - $j = 0; - while ($j < $res[$i]['asteriskincludeline']['count']) { - @$line = $res[$i]['asteriskincludeline'][$j]; - $dialplans[$context]['includes'][$j] = $line; - $j++; - } - } - - # Handle ignorepat - if (isset($res[$i]['asteriskignorepat'])) { - $j = 0; - while ($j < $res[$i]['asteriskignorepat']['count']) { - @$line = $res[$i]['asteriskignorepat'][$j]; - $dialplans[$context]['ignorepats'][$j] = $line; - $j++; - } - } - # Handle ignorepat - if (isset($res[$i]['asteriskextensionbareline'])) { - $j = 0; - while ($j < $res[$i]['asteriskextensionbareline']['count']) { - @$line = $res[$i]['asteriskextensionbareline'][$j]; - $dialplans[$context]['barelines'][$j] = $line; - $j++; - } - } - - # Increment object - $i++; - } - return $dialplans[$context]; - } - - /** - * Get the limits for the current user, the user's context, and global - * Return the most specific values in every case. Return default values - * where no data is found. If $extension is specified, $context must - * also be specified. - * - * @param optional string $context Context to search - * - * @param optional string $extension Extension/user to search - * - * @return array Array with elements indicating various limits - */ - # FIXME Figure out how this fits into Shout/Congregation better - public function getLimits($context = null, $extension = null) - { - - $limits = array('telephonenumbersmax', - 'voicemailboxesmax', - 'asteriskusersmax'); - - if(!is_null($extension) && is_null($context)) { - return PEAR::raiseError("Extension specified but no context " . - "given."); - } - - if (!is_null($context) && isset($limits[$context])) { - if (!is_null($extension) && - isset($limits[$context][$extension])) { - return $limits[$context][$extension]; - } - return $limits[$context]; - } - - # Set some default limits (to unlimited) - static $cachedlimits = array(); - # Initialize the limits with defaults - if (count($cachedlimits) < 1) { - foreach ($limits as $limit) { - $cachedlimits[$limit] = 99999; - } - } - - # Collect the global limits - $res = @ldap_search($this->_LDAP, - SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'], - '(&(objectClass=asteriskLimits)(cn=globals))', - $limits); - - if (!$res) { - return PEAR::raiseError('Unable to search the LDAP server for ' . - 'global limits'); - } - - $res = ldap_get_entries($this->_LDAP, $res); - # There should only have been one object returned so we'll just take the - # first result returned - if ($res['count'] > 0) { - foreach ($limits as $limit) { - if (isset($res[0][$limit][0])) { - $cachedlimits[$limit] = $res[0][$limit][0]; - } - } - } else { - return PEAR::raiseError("No global object found."); - } - - # Get limits for the context, if provided - if (isset($context)) { - $res = ldap_search($this->_LDAP, - SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'], - "(&(objectClass=asteriskLimits)(cn=$context))"); - - if (!$res) { - return PEAR::raiseError('Unable to search the LDAP server ' . - "for $context specific limits"); - } - - $cachedlimits[$context][$extension] = array(); - if ($res['count'] > 0) { - foreach ($limits as $limit) { - if (isset($res[0][$limit][0])) { - $cachedlimits[$context][$limit] = $res[0][$limit][0]; - } else { - # If no value is provided use the global limit - $cachedlimits[$context][$limit] = $cachedlimits[$limit]; - } - } - } else { - - foreach ($limits as $limit) { - $cachedlimits[$context][$limit] = - $cachedlimits[$limit]; - } - } - - if (isset($extension)) { - $res = @ldap_search($this->_LDAP, - SHOUT_USERS_BRANCH.','.$this->_params['basedn'], - "(&(objectClass=asteriskLimits)(voiceMailbox=$extension)". - "(context=$context))"); - - if (!$res) { - return PEAR::raiseError('Unable to search the LDAP server '. - "for Extension $extension, $context specific limits"); - } - - $cachedlimits[$context][$extension] = array(); - if ($res['count'] > 0) { - foreach ($limits as $limit) { - if (isset($res[0][$limit][0])) { - $cachedlimits[$context][$extension][$limit] = - $res[0][$limit][0]; - } else { - # If no value is provided use the context limit - $cachedlimits[$context][$extension][$limit] = - $cachedlimits[$context][$limit]; - } - } - } else { - foreach ($limits as $limit) { - $cachedlimits[$context][$extension][$limit] = - $cachedlimits[$context][$limit]; - } - } - return $cachedlimits[$context][$extension]; - } - return $cachedlimits[$context]; - } - } - - /** - * Save an extension to the LDAP tree - * - * @param string $context Context to which the user should be added - * - * @param string $extension Extension to be saved - * - * @param array $details Phone numbers, PIN, options, etc to be saved - * - * @return TRUE on success, PEAR::Error object on error - */ - public function saveExtension($context, $extension, $details) - { - $ldapKey = &$this->_ldapKey; - $appKey = &$this->_appKey; - # FIXME Access Control/Authorization - if (!Shout::checkRights("shout:contexts:$context:extensions", PERMS_EDIT, 1)) { - // FIXME: Allow users to edit themselves - //&& !($details[$appKey] == Auth::getAuth())) { - throw new Shout_Exception(_("Permission denied to save extensions in this context.")); - } - - $contexts = &$this->getContexts(); -// $domain = $contexts[$context]['domain']; - - # Check to ensure the extension is unique within this context - $filter = "(&(objectClass=AstVoicemailMailbox)(context=$context))"; - $reqattrs = array('dn', $ldapKey); - $res = @ldap_search($this->_LDAP, - SHOUT_USERS_BRANCH . ',' . $this->_params['basedn'], - $filter, $reqattrs); - if (!$res) { - return PEAR::raiseError('Unable to check directory for duplicate extension: ' . - ldap_error($this->_LDAP)); - } - if (($res['count'] > 1) || - ($res['count'] != 0 && - !in_array($res[0][$ldapKey], $details[$appKey]))) { - return PEAR::raiseError('Duplicate extension found. Not saving changes.'); - } - - $entry = array( - 'cn' => $details['name'], - 'sn' => $details['name'], - 'mail' => $details['email'], - 'uid' => $details['email'], - 'voiceMailbox' => $details['newextension'], - 'voiceMailboxPin' => $details['mailboxpin'], - 'context' => $context, - 'asteriskUserDialOptions' => $details['dialopts'], - ); - - if (!empty ($details['telephonenumber'])) { - $entry['telephoneNumber'] = $details['telephonenumber']; - } - - $validusers = &$this->getUsers($context); - if (!isset($validusers[$extension])) { - # Test to see if we're modifying an existing user that has - # no telephone system objectClasses and update that object/user - $rdn = $ldapKey.'='.$details[$appKey].','; - $branch = SHOUT_USERS_BRANCH.','.$this->_params['basedn']; - - # This test is something of a hack. I want a cheap way to check - # for the existance of an object. I don't want to do a full search - # so instead I compare that the dn equals the dn. If the object - # exists then it'll return true. If the object doesn't exist, - # it'll return error. If it ever returns false something wierd - # is going on. - $res = @ldap_compare($this->_LDAP, $rdn.$branch, - $ldapKey, $details[$appKey]); - if ($res === false) { - # We should never get here: a DN should ALWAYS match itself - return PEAR::raiseError("Internal Error: " . __FILE__ . " at " . - __LINE__); - } elseif ($res === true) { - # The object/user exists but doesn't have the Asterisk - # objectClasses - $extension = $details['newextension']; - - # $tmp is the minimal information required to establish - # an account in LDAP as required by the objectClasses. - # The entry will be fully populated below. - $tmp = array(); - $tmp['objectClass'] = array( - 'asteriskUser', - 'asteriskVoiceMailbox' - ); - $tmp['voiceMailbox'] = $extension; - $tmp['context'] = $context; - $res = @ldap_mod_replace($this->_LDAP, $rdn.$branch, $tmp); - if (!$res) { - return PEAR::raiseError("Unable to modify the user: " . - ldap_error($this->_LDAP)); - } - - # Populate the $validusers array to make the edit go smoothly - # below - $validusers[$extension] = array(); - $validusers[$extension][$appKey] = $details[$appKey]; - - # The remainder of the work is done at the outside of the - # parent if() like a normal edit. - - } elseif ($res === -1) { - # We must be adding a new user. - $entry['objectClass'] = array( - 'top', - 'person', - 'organizationalPerson', - 'inetOrgPerson', - 'hordePerson', - 'asteriskUser', - 'asteriskVoiceMailbox' - ); - - # Check to see if the maximum number of users for this context - # has been reached - $limits = $this->getLimits($context); - if (is_a($limits, "PEAR_Error")) { - return $limits; - } - if (count($validusers) >= $limits['asteriskusersmax']) { - return PEAR::raiseError('Maximum number of users reached.'); - } - - $res = @ldap_add($this->_LDAP, $rdn.$branch, $entry); - if (!$res) { - return PEAR::raiseError('LDAP Add failed: ' . - ldap_error($this->_LDAP)); - } - - return true; - } elseif (is_a($res, 'PEAR_Error')) { - # Some kind of internal error; not even sure if this is a - # possible outcome or not but I'll play it safe. - return $res; - } - } - - # Anything after this point is an edit. - - # Check to see if the object needs to be renamed (DN changed) - if ($validusers[$extension][$appKey] != $entry[$ldapKey]) { - $oldrdn = $ldapKey.'='.$validusers[$extension][$appKey]; - $oldparent = SHOUT_USERS_BRANCH.','.$this->_params['basedn']; - $newrdn = $ldapKey.'='.$entry[$ldapKey]; - $res = @ldap_rename($this->_LDAP, "$oldrdn,$oldparent", - $newrdn, $oldparent, true); - if (!$res) { - return PEAR::raiseError('LDAP Rename failed: ' . - ldap_error($this->_LDAP)); - } - } - - # Update the object/user - $dn = $ldapKey.'='.$entry[$ldapKey]; - $dn .= ','.SHOUT_USERS_BRANCH.','.$this->_params['basedn']; - $res = @ldap_modify($this->_LDAP, $dn, $entry); - if (!$res) { - return PEAR::raiseError('LDAP Modify failed: ' . - ldap_error($this->_LDAP)); - } - - # We must have been successful - return true; - } - - /** - * Deletes a user from the LDAP tree - * - * @param string $context Context to delete the user from - * @param string $extension Extension of the user to be deleted - * - * @return boolean True on success, PEAR::Error object on error - */ - public function deleteUser($context, $extension) - { - $ldapKey = &$this->_ldapKey; - $appKey = &$this->_appKey; - - if (!Shout::checkRights("shout:contexts:$context:users", - PERMS_DELETE, 1)) { - return PEAR::raiseError("No permission to delete users in this " . - "context."); - } - - $validusers = $this->getUsers($context); - if (!isset($validusers[$extension])) { - return PEAR::raiseError("That extension does not exist."); - } - - $dn = "$ldapKey=".$validusers[$extension][$appKey]; - $dn .= ',' . SHOUT_USERS_BRANCH . ',' . $this->_params['basedn']; - - $res = @ldap_delete($this->_LDAP, $dn); - if (!$res) { - return PEAR::raiseError("Unable to delete $extension from " . - "$context: " . ldap_error($this->_LDAP)); - } - return true; - } - - - /* Needed because uksort can't take a classed function as its callback arg */ - protected function _sortexten($e1, $e2) - { - print "$e1 and $e2\n"; - $ret = Shout::extensort($e1, $e2); - print "returning $ret"; - return $ret; - } - - /** - * Attempts to open a connection to the LDAP server. - * - * @return boolean True on success; exits (Horde::fatal()) on error. - * - * @access private - */ - protected function _connect() - { - if ($this->_connected) { - return; - } - - if (!Horde_Util::extensionExists('ldap')) { - throw new Horde_Exception('Required LDAP extension not found.'); - } - - Horde::assertDriverConfig($this->_params, $this->_params['class'], - array('hostspec', 'basedn', 'writedn')); - - /* Open an unbound connection to the LDAP server */ - $conn = ldap_connect($this->_params['hostspec'], $this->_params['port']); - if (!$conn) { - Horde::logMessage( - sprintf('Failed to open an LDAP connection to %s.', - $this->_params['hostspec']), - __FILE__, __LINE__, PEAR_LOG_ERR); - throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.'); - } - - /* Set hte LDAP protocol version. */ - if (isset($this->_params['version'])) { - $result = ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, - $this->_params['version']); - if ($result === false) { - Horde::logMessage( - sprintf('Set LDAP protocol version to %d failed: [%d] %s', - $this->_params['version'], - ldap_errno($conn), - ldap_error($conn)), - __FILE__, __LINE__, PEAR_LOG_WARNING); - throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn)); - } - } - - /* Start TLS if we're using it. */ - if (!empty($this->_params['tls'])) { - if (!@ldap_start_tls($conn)) { - Horde::logMessage( - sprintf('STARTTLS failed: [%d] %s', - @ldap_errno($this->_ds), - @ldap_error($this->_ds)), - __FILE__, __LINE__, PEAR_LOG_ERR); - } - } - - /* If necessary, bind to the LDAP server as the user with search - * permissions. */ - if (!empty($this->_params['searchdn'])) { - $bind = ldap_bind($conn, $this->_params['searchdn'], - $this->_params['searchpw']); - if ($bind === false) { - Horde::logMessage( - sprintf('Bind to server %s:%d with DN %s failed: [%d] %s', - $this->_params['hostspec'], - $this->_params['port'], - $this->_params['searchdn'], - @ldap_errno($conn), - @ldap_error($conn)), - __FILE__, __LINE__, PEAR_LOG_ERR); - throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn)); - } - } - - /* Store the connection handle at the instance level. */ - $this->_LDAP = $conn; - } - -} diff --git a/lib/Driver/Sql.php b/lib/Driver/Sql.php deleted file mode 100644 index 8eaac441b..000000000 --- a/lib/Driver/Sql.php +++ /dev/null @@ -1,262 +0,0 @@ -_connect(); - } - - public function getContexts() - { - $this->_connect(); - - $sql = 'SELECT context FROM %s'; - $sql = sprintf($sql, $this->_params['table']); - $vars = array(); - - $result = $this->_db->query($sql, $vars); - if ($result instanceof PEAR_Error) { - throw Shout_Exception($result); - } - - $row = $result->fetchRow(DB_FETCHMODE_ASSOC); - if ($row instanceof PEAR_Error) { - throw Shout_Exception($row); - } - - $contexts = array(); - while ($row && !($row instanceof PEAR_Error)) { - /* Add this new foo to the $_foo list. */ - $contexts[] = $row['context']; - - /* Advance to the new row in the result set. */ - $row = $result->fetchRow(DB_FETCHMODE_ASSOC); - } - - $result->free(); - return $contexts; - } - - /** - * Get a list of devices for a given context - * - * @param string $context Context in which to search for devicess - * - * @return array Array of devices within this context with their information - * - * @access private - */ - public function getDevices($context) - { - $sql = 'SELECT id, name, alias, callerid, context, mailbox, host, permit, ' . - 'nat, secret, disallow, allow FROM %s WHERE accountcode = ?'; - $sql = sprintf($sql, $this->_params['table']); - $args = array($context); - $result = $this->_db->query($sql, $args); - if ($result instanceof PEAR_Error) { - throw new Shout_Exception($result); - } - - $row = $result->fetchRow(DB_FETCHMODE_ASSOC); - if ($row instanceof PEAR_Error) { - throw new Shout_Exception($row); - } - - $devices = array(); - while ($row && !($row instanceof PEAR_Error)) { - // Asterisk uses the "name" field to indicate the registration - // identifier. We use the field "alias" to put a friendly name on - // the device. Thus devid -> name and name => alias - $devid = $row['name']; - $row['devid'] = $devid; - $row['name'] = $row['alias']; - unset($row['alias']); - - // Trim off the context from the mailbox number - list($row['mailbox']) = explode('@', $row['mailbox']); - - // Hide the DB internal ID from the front-end - unset($row['id']); - - $devices[$devid] = $row; - - /* Advance to the new row in the result set. */ - $row = $result->fetchRow(DB_FETCHMODE_ASSOC); - } - - $result->free(); - return $devices; - } - - /** - * Save a device (add or edit) to the backend. - * - * @param string $context The context in which this device is valid - * @param array $info Array of device details - */ - public function saveDevice($context, $info) - { - // See getDevices() for an explanation of these conversions - $info['alias'] = $info['name']; - $info['mailbox'] = $info['mailbox'] . '@' . $context; - - if ($info['devid']) { - // This is an edit - $info['name'] = $info['devid']; - $sql = 'UPDATE %s SET '; - } else { - // This is an add. Generate a new unique ID and secret - $devid = $context . uniqid(); - $secret = md5(uniqid(mt_rand)); - $sql = 'INSERT INTO %s (name, accountcode, callerid, mailbox, ' . - 'secret, alias, canreinvite, nat, type) ' . - ' VALUES (?, ?, ?, ?, ?, ?, "no", "yes", "peer")'; - - } - - - } - - /** - * Get a list of users valid for the contexts - * - * @param string $context Context on which to search - * - * @return array User information indexed by voice mailbox number - */ - public function getExtensions($context) - { - throw new Shout_Exception("Not implemented yet."); - } - - /** - * Save a user to the LDAP tree - * - * @param string $context Context to which the user should be added - * - * @param string $extension Extension to be saved - * - * @param array $userdetails Phone numbers, PIN, options, etc to be saved - * - * @return TRUE on success, PEAR::Error object on error - */ - public function saveExtension($context, $extension, $userdetails) - { - throw new Shout_Exception("Not implemented."); - } - - /** - * Deletes a user from the LDAP tree - * - * @param string $context Context to delete the user from - * @param string $extension Extension of the user to be deleted - * - * @return boolean True on success, PEAR::Error object on error - */ - public function deleteExtension($context, $extension) - { - throw new Shout_Exception("Not implemented."); - } - - /** - * Attempts to open a persistent connection to the SQL server. - * - * @throws Horde_Exception - */ - protected function _connect() - { - if ($this->_connected) { - return; - } - - Horde::assertDriverConfig($this->_params, $this->_params['class'], - array('phptype', 'charset', 'table')); - - if (!isset($this->_params['database'])) { - $this->_params['database'] = ''; - } - if (!isset($this->_params['username'])) { - $this->_params['username'] = ''; - } - if (!isset($this->_params['hostspec'])) { - $this->_params['hostspec'] = ''; - } - - /* Connect to the SQL server using the supplied parameters. */ - $this->_write_db = DB::connect($this->_params, - array('persistent' => !empty($this->_params['persistent']))); - if ($this->_write_db instanceof PEAR_Error) { - throw Horde_Exception($this->_write_db); - } - - // Set DB portability options. - switch ($this->_write_db->phptype) { - case 'mssql': - $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); - break; - - default: - $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); - } - - /* Check if we need to set up the read DB connection seperately. */ - if (!empty($this->_params['splitread'])) { - $params = array_merge($this->_params, $this->_params['read']); - $this->_db = DB::connect($params, - array('persistent' => !empty($params['persistent']))); - if ($this->_db instanceof PEAR_Error) { - throw Horde_Exception($this->_db); - } - - // Set DB portability options. - switch ($this->_db->phptype) { - case 'mssql': - $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); - break; - - default: - $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); - } - - } else { - /* Default to the same DB handle for the writer too. */ - $this->_db = $this->_write_db; - } - - $this->_connected = true; - } - - /** - * Disconnects from the SQL server and cleans up the connection. - */ - protected function _disconnect() - { - if ($this->_connected) { - $this->_connected = false; - $this->_db->disconnect(); - $this->_write_db->disconnect(); - } - } - -} diff --git a/lib/Exception.php b/lib/Exception.php deleted file mode 100644 index 301bcf7d8..000000000 --- a/lib/Exception.php +++ /dev/null @@ -1,4 +0,0 @@ - - * - * See the enclosed file LICENSE for license information (GPL). If you - * did not receive this file, see http://www.horde.org/licenses/gpl.php. - * - * @package Shout - */ - -class DeviceDetailsForm extends Horde_Form { - - function __construct(&$vars) - { - global $shout_extensions; - - if ($vars->exists('devid')) { - $formtitle = "Edit Device"; - $devid = $vars->get('devid'); - $edit = true; - } else { - $formtitle = "Add Device"; - $edit = false; - } - - parent::__construct($vars, _("$formtitle - Context: $context")); - $this->addHidden('', 'action', 'text', true); - $vars->set('action', 'save'); - if ($edit) { - $this->addHidden('', 'devid', 'text', true); - - } - $this->addVariable(_("Device Name"), 'name', 'text', false); - $this->addVariable(_("Mailbox"), 'mailbox', 'int', false); - $this->addVariable(_("CallerID"), 'callerid', 'text', false); - - - return true; - } - -} \ No newline at end of file diff --git a/lib/Forms/ExtensionForm.php b/lib/Forms/ExtensionForm.php deleted file mode 100644 index 444319dda..000000000 --- a/lib/Forms/ExtensionForm.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * 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 $shout_extensions - * @param $vars - * @return - */ - function __construct(&$vars) - { - global $shout_extensions; - $context = $vars->get('context'); - if ($vars->exists('extension')) { - $formtitle = "Edit User"; - $extension = $vars->get('extension'); - } else { - $formtitle = "Add User"; - } - - parent::__construct($vars, _("$formtitle - Context: $context")); - $this->addHidden('', 'action', 'text', true); - $vars->set('action', 'save'); - $this->addHidden('', 'extension', 'int', true); - $vars->set('newextension', $extension); - $this->addVariable(_("Full Name"), 'name', 'text', true); - $this->addVariable(_("Extension"), 'newextension', 'int', true); - $this->addVariable(_("E-Mail Address"), 'email', 'email', true); - $this->addVariable(_("Pager E-Mail Address"), 'pageremail', 'email', false); - $this->addVariable(_("PIN"), 'mailboxpin', 'int', true); - - return true; - } - - /** - * Process this form, saving its information to the backend. - * - * @param string $context Context in which to execute this save - * FIXME: is there a better way to get the $context and $shout_extensions? - */ - function execute($context) - { - global $shout_extensions; - - $extension = $this->vars->get('extension'); - - # FIXME: Input Validation (Text::??) - $details = array( - 'newextension' => $vars->get('newextension'), - 'name' => $vars->get('name'), - 'mailboxpin' => $vars->get('mailboxpin'), - 'email' => $vars->get('email'), - ); - - $res = $shout_extensions->saveExtension($context, $extension, $details); - } - -} \ No newline at end of file diff --git a/lib/Shout.php b/lib/Shout.php deleted file mode 100644 index 8eed4aa8a..000000000 --- a/lib/Shout.php +++ /dev/null @@ -1,136 +0,0 @@ - - * - * 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 - * @version $Revision: 94 $ - * @since Shout 0.1 - * @package Shout - */ - -class Shout -{ - var $applist = array(); - var $_applist_curapp = ''; - var $_applist_curfield = ''; - - /** - * Build Shout's list of menu items. - * - * @access public - */ - static public function getMenu($returnType = 'object') - { - global $conf, $context, $section, $action; - - require_once 'Horde/Menu.php'; - - $menu = new Horde_Menu(HORDE_MENU_MASK_ALL); - - $menu->add(Horde::applicationUrl('extensions.php'), _("Extensions"), "user.png"); - $menu->add(Horde::applicationUrl('devices.php'), _("Devices"), "shout.png"); - $menu->add(Horde::applicationUrl('routes.php'), _("Call Paths")); - - - if ($returnType == 'object') { - return $menu; - } else { - return $menu->render(); - } - } - - /** - * Generate the tabs at the top of each Shout pages - * - * @param &$vars Reference to the passed in variables - * - * @return object Horde_UI_Tabs - */ - static public function getTabs($context, &$vars) - { - global $shout; - $perms = Horde_Perms::singleton(); - - $permprefix = 'shout:contexts:' . $context; - - $tabs = new Horde_UI_Tabs('section', $vars); - - if (Shout::checkRights($permprefix . ':extensions', null, 1)) { - $url = Horde::applicationUrl('extensions.php'); - $tabs->addTab(_("_Extensions"), $url, 'extensions'); - } - - if (Shout::checkRights($permprefix . ':dialplan', null, 1)) { - $url = Horde::applicationUrl('dialplan.php'); - $tabs->addTab(_("_Automated Attendant"), $url, 'dialplan'); - } - - if (Shout::checkRights($permprefix . ':conference', null, 1)) { - $url = Horde::applicationUrl('conference.php'); - $tabs->addTab(_("_Conference Rooms"), $url, 'conference'); - } - - if (Shout::checkRights($permprefix . ':moh', null, 1)) { - $url = Horde::applicationUrl('moh.php'); - $tabs->addTab(_("_Music on Hold"), $url, 'moh'); - } - - return $tabs; - } - - /** - * Checks for the given permissions for the current user on the given - * permission. Optionally check for higher-level permissions and ultimately - * test for superadmin priveleges. - * - * @param string $permname Name of the permission to check - * - * @param optional int $permmask Bitfield of permissions to check for - * - * @param options int $numparents Check for the same permissions this - * many levels up the tree - * - * @return boolean the effective permissions for the user. - */ - static public function checkRights($permname, $permmask = null, $numparents = 0) - { - if (Horde_Auth::isAdmin()) { return true; } - - $perms = Horde_Perms::singleton(); - if ($permmask === null) { - $permmask = PERMS_SHOW|PERMS_READ; - } - - # Default deny all permissions - $user = 0; - $superadmin = 0; - - $superadmin = $perms->hasPermission('shout:superadmin', - Horde_Auth::getAuth(), $permmask); - - while ($numparents >= 0) { - $tmpuser = $perms->hasPermission($permname, - Horde_Auth::getAuth(), $permmask); - - $user = $user | $tmpuser; - if ($numparents > 0) { - $pos = strrpos($permname, ':'); - if ($pos) { - $permname = substr($permname, 0, $pos); - } - } - $numparents--; - } - $test = $superadmin | $user; -$ret = ($test & $permmask) == $permmask; -print "Shout::checkRights() returning $ret"; - return ($test & $permmask) == $permmask; - } -} diff --git a/lib/api.php b/lib/api.php deleted file mode 100644 index ce6d59f23..000000000 --- a/lib/api.php +++ /dev/null @@ -1,130 +0,0 @@ - array(), - 'type' => '{urn:horde}stringArray', -); - -$_services['attributes'] = array( - 'args' => array(), - 'type' => '{urn:horde}stringArray', -); - -function _shout_perms() -{ - static $perms = array(); - if (!empty($perms)) { - return $perms; - } - - @define('SHOUT_BASE', dirname(__FILE__) . '/..'); - require_once SHOUT_BASE . '/lib/base.php'; - - $perms['tree']['shout']['superadmin'] = false; - $perms['title']['shout:superadmin'] = _("Super Administrator"); - - $contexts = $shout->getContexts(); - - $perms['tree']['shout']['contexts'] = false; - $perms['title']['shout:contexts'] = _("Contexts"); - - // Run through every contact source. - foreach ($contexts as $context => $contextInfo) { - $perms['tree']['shout']['contexts'][$context] = false; - $perms['title']['shout:contexts:' . $context] = $context; - - foreach( - array( - 'users' => 'Users', - 'dialplan' => 'Dialplan', - 'moh' => 'Music on Hold', - 'conferences' => 'Conferencing', - ) - as $module => $modname) { - $perms['tree']['shout']['contexts'][$context][$module] = false; - $perms['title']["shout:contexts:$context:$module"] = $modname; - } - } - -// function _shout_getContexts($searchfilters = SHOUT_CONTEXT_ALL, -// $filterperms = null) - - return $perms; -} - -function _shout_attributes() -{ - // See CONGREGATION_BASE/docs/api.txt for information on the structure - // of this array. - $shoutAttributes = array( - 'description' => 'Phone System User Settings', - 'attributes' => array( - 'extension' => array( - 'name' => 'Extension', - 'description' => 'Phone System Extension (doubles as Voice Mailbox Number', - 'type' => 'int', - 'size' => 3, - 'keys' => array( - 'ldap' => 'asteriskVoiceMailbox', - ), - 'limit' => 1, - 'required' => true, - 'infoset' => 'basic', - ), - - 'mailboxpin' => array( - 'name' => 'Mailbox PIN', - 'description' => 'Voice Mailbox PIN', - 'type' => 'int', - 'size' => 12, - 'keys' => array( - 'ldap' => 'asteriskVoiceMailboxPIN', - ), - 'limit' => 1, - 'required' => true, - 'infoset' => 'basic', - ), - - 'phonenumbers' => array( - 'name' => 'Telephone Numbers', - 'description' => 'Dialout phone numbers', - 'type' => 'cellphone', // WHY does Horde have cellphone but NOT - // telephone or just phonenumber??? - 'size' => 12, - 'keys' => array( - 'ldap' => 'telephoneNumber', - ), - 'limit' => 5, - 'required' => true, - 'infoset' => 'basic', - ), - - 'dialstring' => array( - 'name' => 'Dial String', - 'description' => 'Asterisk raw dial string', - 'type' => 'cellphone', // WHY does Horde have cellphone but NOT - // telephone or just phonenumber??? - 'size' => 12, - 'keys' => array( - 'ldap' => 'telephoneNumber', - ), - 'limit' => 5, - 'required' => true, - 'infoset' => 'restricted', - ), - ), - ); - - return $shoutAttributes; -} diff --git a/lib/base.php b/lib/base.php deleted file mode 100644 index 4ec1df0c2..000000000 --- a/lib/base.php +++ /dev/null @@ -1,87 +0,0 @@ -pushApp('shout', array('check_perms' => true, 'logintasks' => true)); -} catch (Horde_Exception $e) { - Horde::authenticationFailureRedirect('shout', $e); -} - -$conf = &$GLOBALS['conf']; -@define('SHOUT_TEMPLATES', $registry->get('templates')); - -// Ensure Shout is properly configured before use -$shout_configured = (@is_readable(SHOUT_BASE . '/config/conf.php')); -if (!$shout_configured) { - Horde_Test::configFilesMissing('Shout', SHOUT_BASE, array('conf.php')); -} - -$notification = Horde_Notification::singleton(); -$notification->attach('status'); - -//// Shout base libraries. -//require_once SHOUT_BASE . '/lib/Shout.php'; -//require_once SHOUT_BASE . '/lib/Driver.php'; -// -//// Form libraries. -//require_once 'Horde/Form.php'; -//require_once 'Horde/Form/Renderer.php'; -// -//// Variable handling libraries -//require_once 'Horde/Variables.php'; -//require_once 'Horde/Text/Filter.php'; -// -//// UI classes. -//require_once 'Horde/UI/Tabs.php'; - -$shout_storage = Shout_Driver::factory('storage'); -$shout_extensions = Shout_Driver::factory('extensions'); -$shout_devices = Shout_Driver::factory('devices'); - -$context = Horde_Util::getFormData('context'); -$section = Horde_Util::getFormData('section'); - -try { - $contexts = $shout_storage->getContexts(); -} catch (Shout_Exception $e) { - $notification->push($e); - $contexts = false; -} - -if (count($contexts) == 1) { - // Default to the user's only context - $context = $contexts[0]; -} elseif (!empty($context) && !in_array($context, $contexts)) { - $notification->push('You do not have permission to access that context.', 'horde.error'); - $context = false; -} elseif (!empty($context)) { - $notification->push("Please select a context to continue.", 'horde.info'); - $context = false; -} - -$_SESSION['shout']['context'] = $context; \ No newline at end of file diff --git a/lib/shoutAtts.php b/lib/shoutAtts.php deleted file mode 100644 index cc70a9094..000000000 --- a/lib/shoutAtts.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - */ - -$shoutAttributes = array(); -$shoutAttributes['description'] = 'Phone System Options'; -$shoutAttributes['permsNode'] = 'shout:contexts:somethingOrOther'; -$shoutAttributes['attributes']['extension']['description'] = 'Internal Phone Extension'; -$shoutAttributes['attributes']['extension']['type'] = 'integer'; -$shoutAttributes['attributes']['extension']['size'] = 3; // max length for this particular string -$shoutAttributes['attributes']['extension']['ldapKey'] = 'voiceextensionsomethingIhavenoidea'; - -$shoutAttributes['attributes']['mailboxpin']['description'] = 'Mailbox PIN'; -$shoutAttributes['attributes']['mailboxpin']['type'] = 'integer'; -$shoutAttributes['attributes']['mailboxpin']['size'] = 4; -$shoutAttributes['attributes']['mailboxpin']['ldapKey'] = 'voicemailboxpin'; - -$shoutAttributes['attributes']['phonenumbers']['description'] = 'Phone Numbers'; -$shoutAttributes['attributes']['phonenumbers']['type'] = 'array'; -$shoutAttributes['attributes']['phonenumbers']['size'] = 1; -$shoutAttributes['attributes']['phonenumbers']['arrayType'] = 'string'; -$shoutAttributes['attributes']['phonenumbers']['ldapKey'] = 'asteriskuserphonenumbers'; - -?> diff --git a/lib/version.php b/lib/version.php deleted file mode 100644 index b855f6a05..000000000 --- a/lib/version.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/moh.php b/moh.php deleted file mode 100644 index 3c89ebd82..000000000 --- a/moh.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - * - * @package shout - */ -if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) { - header('Location: /'); - exit(); -} - -$title = _('Music on Hold'); - -require SHOUT_TEMPLATES . '/common-header.inc'; -require SHOUT_TEMPLATES . '/menu.inc'; - -$notification->notify(); - -$tabs->render($section); - -require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/security.php b/security.php deleted file mode 100644 index 11f312697..000000000 --- a/security.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - * - * @package shout - */ - -if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) { - header('Location: /'); - exit(); -} - -$title = _('Security'); - -require SHOUT_TEMPLATES . '/common-header.inc'; -require SHOUT_TEMPLATES . '/menu.inc'; - -$notification->notify(); - -$tabs->render($section); - -Shout::getApplist(); - -require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/shout/TODO b/shout/TODO new file mode 100644 index 000000000..c53e828b7 --- /dev/null +++ b/shout/TODO @@ -0,0 +1 @@ +* Convert forms to Beatnik style associative arrays. 2006-01-26 bklang diff --git a/shout/config/applist.xml.dist b/shout/config/applist.xml.dist new file mode 100644 index 000000000..07f72e4e1 --- /dev/null +++ b/shout/config/applist.xml.dist @@ -0,0 +1,862 @@ + + + Say Alpha + SayAlpha(string): Spells the passed string + + + + + Send a FAX file + 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. + + + + + Enter voicemail system + 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. + + + + + Strip Least Significant Digits + 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. + + + + + Change monitoring filename of a channel + 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. + + + + + Background a file with talk detect + 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. + + + + + Say Number + SayNumber(digits[,gender]): Says the passed number. SayNumber is using +the current language setting for the channel. (See app SetLanguage). + + + + + Provide directory of voicemail extensions + 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. + + + + + Set CallerID + 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 + + + + + Indicate congestion and stop + 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. + + + + Indicate busy condition and stop + 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. + + + + Park yourself + 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. + + + + + Leave a voicemail message + 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. + + + + + Waits for some time + 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) + + + + + Change the presentation for the callerid + Callingpres(number): Changes the presentation for the callerid. Should be called before placing an outgoing call + + + + + Play Music On Hold indefinitely + 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. + + + + + Set absolute maximum time of call + AbsoluteTimeout(seconds): Set the absolute maximum amount of time permitted +for a call. A setting of 0 disables the timeout. Always returns 0. + + + + + Enter voicemail system + 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. + + + + + Play a file while awaiting extension + 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. + + + + + Send arbitrary text to verbose output + Verbose([<level>|]<message>) + level must be an integer value. If not specified, defaults to 0. Always returns 0. + + + + + Stop monitoring a channel + StopMonitor +Stops monitoring a channel. Has no effect if the channel is not monitored + + + + + (Deprecated) Save substring digits in a given variable + (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 + + + + + MeetMe participant count + 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. + + + + + Set CallerID Name + 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 + + + + + Flashes a Zap Trunk + 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 + + + + + Append trailing digits + 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. + + + + + Sets account code + SetAccount([account]): Set the channel account code for billing +purposes. Always returns 0. + + + + + Say Phonetic + SayPhonetic(string): Spells the passed string with phonetic alphabet + + + + + Monitor a channel + 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. + + + + + Wait, playing Music On Hold + 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. + + + + + Answer a channel if ringing + Answer(): If the channel is ringing, answer it, otherwise do nothing. +Returns 0 unless it tries to answer the channel and fails. + + + + + Echo audio read back to the user + 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. + + + + + Say Digits + SayDigits(digits): Says the passed digits. SayDigits is using the +current language setting for the channel. (See app setLanguage) + + + + + Check if vmbox exists + MailboxExists(mailbox[@context]): Conditionally branches to priority n+101 +if the specified voice mailbox exists. + + + + + Goto a particular priority, extension, or context + 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. + + + + + MeetMe conference bridge + 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 + + + + + Set global variable to value + SetGlobalVar(#n=value): Sets global variable n to value. Global +variable are available across channels. + + + + + Soft Hangup Application + SoftHangup(Technology/resource) +Hangs up the requested channel. Always returns 0 + + + + + Set the CDR user field + [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 + + + + + Indicate ringing tone + Ringing(): Request that the channel indicate ringing tone to the user. +Always returns 0. + + + + + Execute a system command + 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. + + + + + Leave a voicemail message + 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. + + + + + Set maximum timeout between digits + 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. + + + + + Scan Zap channels to monitor calls + ZapScan allows a call center manager to monitor Zap channels in +a convenient way. Use '#' to select the next channel and use '*' to exit + + + + + Try executing a system command + 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. + + + + + Sets AMA Flags + SetAMAFlags([flag]): Set the channel AMA Flags for billing +purposes. Always returns 0. + + + + + Wait for Ring Application + 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 + + + + + Prepend leading digits + 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. + + + + + Append to the CDR user field + [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 + + + + + Lookup caller name from TXT record + 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. + + + + + Receive a FAX to a file + 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. + + + + + Transfer caller to remote extension + 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. + + + + + Set default Music On Hold class + 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. + + + + + MeetMe conference Administration + 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 + + + + + Set variable to value + Setvar(#n=value): Sets channel specific variable n to value + + + + Record to a file + 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. + + + + + Set CallerID Number + 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 + + + + + Resets the Call Data Record + 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. + + + + + Play a file + 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. + + + + + Macro Implementation + 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. + + + + + Change the dtmfmode for a SIP call + SIPDtmfMode(inband|info|rfc2833): Changes the dtmfmode for a SIP call + + + + + Waits for some time + 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) + + + + + Place a call and connect to the current channel + 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 + + + + + Answer a parked call + 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. + + + + + Set maximum timeout awaiting response + 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. + + + + + Unconditional hangup + Hangup(): Unconditionally hangs up a given channel by returning -1 always. + + + + + Conditional goto on current time + 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. + + + + No operation + NoOp(): No-operation; Does nothing. + + + + Set CallerID Presentation + 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 + + + + + + Sets user language + 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. + + + + + Make sure asterisk doesn't save CDR for a certain call + NoCDR(): makes sure there won't be any CDR written for a certain call + + + + Strip leading digits + 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. + + + + + Indicate progress + Progress(): Request that the channel indicate in-band progress is +available to the user. +Always returns 0. + + + + + Barge in (monitor) Zap channel + 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. + + + + Conditional goto + 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. + + + \ No newline at end of file diff --git a/shout/config/conf.xml b/shout/config/conf.xml new file mode 100644 index 000000000..317511531 --- /dev/null +++ b/shout/config/conf.xml @@ -0,0 +1,54 @@ + + + + + + Context Storage + Sql + + + + contexts + + + + + + + + Extension Storage + Ldap + + + + + + + + + + + + + + + + + Device Storage + Ldap + + + + + + + + + sip_peers + + + + + + + diff --git a/shout/devices.php b/shout/devices.php new file mode 100644 index 000000000..cac6319b0 --- /dev/null +++ b/shout/devices.php @@ -0,0 +1,80 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + */ +@define('SHOUT_BASE', dirname(__FILE__)); +require_once SHOUT_BASE . '/lib/base.php'; +require_once SHOUT_BASE . '/lib/Forms/DeviceForm.php'; + +$action = Horde_Util::getFormData('action'); +$devices = $shout_devices->getDevices($context); +$devid = Horde_Util::getFormData('devid'); +$vars = Horde_Variables::getDefaultVariables(); + +//$tabs = Shout::getTabs($context, $vars); + +$RENDERER = new Horde_Form_Renderer(); + +$section = 'devices'; +$title = _("Devices: "); + +switch ($action) { + case 'save': + $Form = new DeviceDetailsForm($vars); + + // Show the list if the save was successful, otherwise back to edit. + if ($Form->isSubmitted() && $Form->isValid()) { + try { + $shout_devices->saveDevice($Form->getVars()); + $notification->push(_("Device settings saved.")); + } catch (Exception $e) { + $notification->push($e); + } + $action = 'list'; + break; + } else { + $action = 'edit'; + } + case 'add': + case 'edit': + if ($action == 'add') { + $title .= _("New Device"); + // Treat adds just like an empty edit + $action = 'edit'; + } else { + $title .= sprintf(_("Edit Device %s"), $extension); + + } + + $FormName = 'DeviceDetailsForm'; + $vars = new Horde_Variables($devices[$devid]); + $Form = new DeviceDetailsForm($vars); + + $Form->open($RENDERER, $vars, Horde::applicationUrl('devices.php'), 'post'); + + break; + + + case 'delete': + $notification->push("Not supported."); + break; + + case 'list': + default: + $action = 'list'; + $title .= _("List Users"); +} + +require SHOUT_TEMPLATES . '/common-header.inc'; +require SHOUT_TEMPLATES . '/menu.inc'; + +$notification->notify(); + +//echo $tabs->render($section); + +require SHOUT_TEMPLATES . '/devices/' . $action . '.inc'; + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/shout/dialplan.php b/shout/dialplan.php new file mode 100644 index 000000000..883d2c1cd --- /dev/null +++ b/shout/dialplan.php @@ -0,0 +1,57 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + * + * @package shout + */ + +if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) { + header('Location: /'); + exit(); +} + +require_once SHOUT_BASE . '/lib/Dialplan.php'; +$dialplan = &$shout->getDialplan($context); + +// Set up the tree. +$dpgui = Shout_Dialplan::singleton('x', $dialplan); + +//$action = Horde_Util::getFormData("action"); +// $action = 'manager'; + +$title = _("Dialplan Manager"); + +require SHOUT_TEMPLATES . '/common-header.inc'; +require SHOUT_TEMPLATES . '/menu.inc'; + +$notification->notify(); + +echo $tabs->render($section); + +// require SHOUT_BASE . "/dialplan/$action.php"; + +require SHOUT_TEMPLATES . '/dialplan/manager.inc'; + +// Horde::addScriptFile('httpclient.js', 'horde', true); +// Horde::addScriptFile('hideable.js', 'horde', true); +// require HORDE_TEMPLATES . '/common-header.inc'; +// require HORDE_TEMPLATES . '/portal/sidebar.inc'; + + +// require SHOUT_TEMPLATES . "/dialplan/dialplanlist.inc"; + + + + + + + + + + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/shout/dialplan/edit.php b/shout/dialplan/edit.php new file mode 100644 index 000000000..18ef2b4c9 --- /dev/null +++ b/shout/dialplan/edit.php @@ -0,0 +1,47 @@ + + * + * See the enclosed file LICENSE for license information (GPL). If you + * did not receive this file, see http://www.horde.org/licenses/gpl.php. + */ +@define('SHOUT_BASE', dirname(__FILE__) . '/..'); +require_once SHOUT_BASE . '/lib/Dialplan.php'; +require_once 'Horde/Variables.php'; + +$RENDERER = &new Horde_Form_Renderer(); + +$empty = ''; + +$vars = &Variables::getDefaultVariables($empty); +$formname = $vars->get('formname'); +$context = $vars->get('context'); +$extension = $vars->get('extension'); +$dialplan = &$shout->getDialplan($context); + +$ExtensionDetailsForm = &Horde_Form::singleton('ExtensionDetailsForm', $vars); +$ExtensionDetailsFormValid = $ExtensionDetailsForm->validate($vars, true); + +$ExtensionDetailsForm->open($RENDERER, $vars, 'dialplan.php', 'post'); +$ExtensionDetailsForm->preserveVarByPost($vars, "section"); +$ExtensionDetailsForm->preserve($vars); +require SHOUT_TEMPLATES . '/table-limiter-begin.inc'; +$RENDERER->beginActive($ExtensionDetailsForm->getTitle()); +$RENDERER->renderFormActive($ExtensionDetailsForm, $vars); +# FIXME Maybe this should be a subclass inheriting from the From/Renderer object +# instead of a simple include? +$i = 0; +require SHOUT_TEMPLATES . '/dialplan/priority-form-begin.inc'; +foreach ($dialplan['extensions'][$extension] as $priority => $application) { + require SHOUT_TEMPLATES . '/dialplan/priority-form-line.inc'; + $i++; +} +require SHOUT_TEMPLATES . '/dialplan/priority-form-end.inc'; +$RENDERER->submit('Add Priority'); +$RENDERER->submit('Add 5 Priorities'); +$RENDERER->submit('Save'); +$RENDERER->end(); +$ExtensionDetailsForm->close($RENDERER); +require SHOUT_TEMPLATES . '/table-limiter-end.inc'; \ No newline at end of file diff --git a/shout/extensions.php b/shout/extensions.php new file mode 100644 index 000000000..295d1658a --- /dev/null +++ b/shout/extensions.php @@ -0,0 +1,99 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + */ +@define('SHOUT_BASE', dirname(__FILE__)); +require_once SHOUT_BASE . '/lib/base.php'; +require_once SHOUT_BASE . '/lib/Forms/ExtensionForm.php'; +//require_once SHOUT_BASE . '/lib/Shout.php'; + +$action = Horde_Util::getFormData('action'); +$extension = Horde_Util::getFormData('extension'); +$extensions = $shout_extensions->getExtensions($context); + +$vars = Horde_Variables::getDefaultVariables(); + +//$tabs = Shout::getTabs($context, $vars); + +$RENDERER = new Horde_Form_Renderer(); + +$section = 'extensions'; +$title = _("Extensions: "); + +switch ($action) { +case 'save': + $title .= sprintf(_("Save Extension %s"), $extension); + $FormName = $vars->get('formname'); + + $Form = &Horde_Form::singleton($FormName, $vars); + + $FormValid = $Form->validate($vars, true); + + if ($Form->isSubmitted() && $FormValid) { + // Form is Valid and Submitted + try { + $Form->execute(); + } catch (Exception $e) { + $notification->push($e); + } + $notification->push(_("User information updated."), + 'horde.success'); + break; + } else { + $action = 'edit'; + // Fall-through to the "edit" action + } + +case 'add': +case 'edit': + if ($action == 'add') { + $title .= _("New Extension"); + // Treat adds just like an empty edit + $action = 'edit'; + } else { + $title .= sprintf(_("Edit Extension %s"), $extension); + + } + + $FormName = 'ExtensionDetailsForm'; + $vars = new Horde_Variables($extensions[$extension]); + $Form = &Horde_Form::singleton($FormName, $vars); + + $Form->open($RENDERER, $vars, Horde::applicationUrl('extensions.php'), 'post'); + + break; + +case 'delete': + $title .= sprintf(_("Delete Extension %s"), $extension); + $extension = Horde_Util::getFormData('extension'); + + $res = $shout->deleteUser($context, $extension); + + if (!$res) { + echo "Failed!"; + print_r($res); + } + $notification->push("User Deleted."); + break; + +case 'list': +default: + $action = 'list'; + $title .= _("List Users"); +} + +require SHOUT_TEMPLATES . '/common-header.inc'; +require SHOUT_TEMPLATES . '/menu.inc'; + +$notification->notify(); + +//echo $tabs->render($section); + +require SHOUT_TEMPLATES . '/extensions/' . $action . '.inc'; + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/shout/index.php b/shout/index.php new file mode 100644 index 000000000..4097661e1 --- /dev/null +++ b/shout/index.php @@ -0,0 +1,23 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + * + * @package shout + */ + +@define('SHOUT_BASE', dirname(__FILE__)); +$shout_configured = (is_readable(SHOUT_BASE . '/config/conf.php')); + +if (!$shout_configured) { +require SHOUT_BASE . '/../lib/Test.php'; + Horde_Test::configFilesMissing('Shout', SHOUT_BASE, + array('conf.php')); +} + +require_once SHOUT_BASE . '/lib/base.php'; +header('Location: ' . Horde::applicationUrl('extensions.php')); diff --git a/shout/lib/Dialplan.php b/shout/lib/Dialplan.php new file mode 100644 index 000000000..0549b3294 --- /dev/null +++ b/shout/lib/Dialplan.php @@ -0,0 +1,227 @@ + + * + * 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 + * + * 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 + * @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 '
'."\n"; + $this->_tree->renderTree(true); + print '
'."\n"; + print ' Back to Top'."\n"; + print '
'."\n"; + return true; + } + + function generateAppList() + { + $applist = Shout::getApplist(); + print ''."\n"; + return true; + } + + function renderExtensions() + { + if(!isset($this->_dialplan['extensions'])) { + print '
'."\n"; + print '
No Configured Extensions
'."\n"; + print '
'."\n"; + } else { + print ''."\n"; + print ''."\n"; + + print '
'."\n"; + print '
'."\n"; + $e = 0; + foreach($this->_dialplan['extensions'] as $extension => $priorities) { + print '
'; + print ''."\n"; + print '
'."\n"; + print '
'."\n"; + $e++; + print '
'."\n"; + print '
'."\n"; + print ''."\n"; + } + print '
'."\n"; + print '
'."\n"; + } + } +} \ No newline at end of file diff --git a/shout/lib/Driver.php b/shout/lib/Driver.php new file mode 100644 index 000000000..421afd3e2 --- /dev/null +++ b/shout/lib/Driver.php @@ -0,0 +1,160 @@ + + * + * 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 + * @version $Revision: 76 $ + * @since Shout 0.1 + * @package Shout + */ + +class Shout_Driver { + + /** + * Hash containing connection parameters. + * + * @var array $_params + */ + var $_params = array(); + + function Shout_Driver($params = array()) + { + $this->_params = $params; + } + + /** + * Get a list of contexts from the instantiated driver and filter + * the returned contexts for those which the current user can see/edit + * + * @param optional string $filter Filter for types of contexts to return. + * One of "system" "customer" or "all" + * + * @param optional string $filterperms Filter contexts for given permissions + * + * @return array Contexts valid for this user + * + * @access public + */ + function getContexts($filters = "all", $filterperms = null) + { + throw new Shout_Exception("This function is not implemented."); + } + + /** + * For the given context and type, make sure the context has the + * appropriate properties, that it is effectively of that "type" + * + * @param string $context the context to check type for + * + * @param string $type the type to verify the context is of + * + * @return boolean true of the context is of type, false if not + * + * @access public + */ + function checkContextType($context, $type) + { + throw new Shout_Exception("This function is not implemented."); + } + + /** + * Get a list of users valid for the current context. Return an array + * indexed by the extension. + * + * @param string $context Context for which users should be returned + * + * @return array User information indexed by voice mailbox number + */ + function getUsers($context) + { + throw new Shout_Exception("This function is not implemented."); + } + + /** + * Returns the name of the user's default context + * + * @return string User's default context + */ + function getHomeContext() + { + throw new Shout_Exception("This function is not implemented."); + } + + /** + * Get a context's properties + * + * @param string $context Context to get properties for + * + * @return integer Bitfield of properties valid for this context + */ + function getContextProperties($context) + { + throw new Shout_Exception("This function is not implemented."); + } + + /** + * Get a context's extensions and return as a multi-dimensional associative + * array + * + * @param string $context Context to return extensions for + * + * @return array Multi-dimensional associative array of extensions data + * + */ + function getDialplan($context) + { + throw new Shout_Exception("This function is not implemented."); + } + + /** + * Attempts to return a concrete Shout_Driver instance based on + * $driver. + * + * @param string $driver The type of the concrete Shout_Driver subclass + * to return. The class name is based on the storage + * driver ($driver). The code is dynamically + * included. + * + * @param array $params (optional) A hash containing any additional + * configuration or connection parameters a + * subclass might need. + * + * @return mixed The newly created concrete Shout_Driver instance, or + * false on an error. + */ + function &factory($class, $driver = null, $params = null) + { + if (is_null($driver)) { + $driver = $GLOBALS['conf'][$class]['driver']; + } + + $driver = basename($driver); + + if (is_null($params)) { + if ($GLOBALS['conf'][$class]['params']['driverconfig'] == 'horde') { + $params = array_merge(Horde::getDriverConfig('storage', $driver), + $GLOBALS['conf'][$class]['params']); + } else { + $params = $GLOBALS['conf'][$class]['params']; + } + } + + $params['class'] = $class; + + require_once dirname(__FILE__) . '/Driver/' . $driver . '.php'; + $class = 'Shout_Driver_' . $driver; + if (class_exists($class)) { + return new $class($params); + } else { + return false; + } + } + +} diff --git a/shout/lib/Driver/Ldap.php b/shout/lib/Driver/Ldap.php new file mode 100644 index 000000000..ee0358fde --- /dev/null +++ b/shout/lib/Driver/Ldap.php @@ -0,0 +1,776 @@ +_connect(); + + /* These next lines will translate between indexes used in the + * application and LDAP. The rationale is that translation here will + * help make Congregation more driver-independant. The keys used to + * contruct user arrays should be more appropriate to human-legibility + * (name instead of 'cn' and email instead of 'mail'). This translation + * is only needed because LDAP indexes users based on an arbitrary + * attribute and the application indexes by extension/context. In my + * environment users are indexed by their 'mail' attribute and others + * may index based on 'cn' or 'uid'. Any time a new $prefs['uid'] needs + * to be supported, this function should be checked and possibly + * extended to handle that translation. + */ + switch($this->_params['uid']) { + case 'cn': + $this->_ldapKey = 'cn'; + $this->_appKey = 'name'; + break; + case 'mail': + $this->_ldapKey = 'mail'; + $this->_appKey = 'email'; + break; + case 'uid': + # FIXME Probably a better app key to map here + # There is no value that maps uid to LDAP so we can choose to use + # either extension or name, or anything really. I want to + # support it since it's a very common DN attribute. + # Since it's entirely administrator's preference, I'll + # set it to name for now + $this->_ldapKey = 'uid'; + $this->_appKey = 'name'; + break; + case 'voiceMailbox': + $this->_ldapKey = 'voiceMailbox'; + $this->_appKey = 'extension'; + break; + } + } + + /** + * Get a list of users valid for the contexts + * + * @param string $context Context in which to search + * + * @return array User information indexed by voice mailbox number + */ + public function getExtensions($context) + { + + static $entries = array(); + if (isset($entries[$context])) { + return $entries[$context]; + } + + $this->_params['basedn']; + + $filter = '(&'; + $filter .= '(objectClass=AsteriskVoiceMail)'; + $filter .= '(AstContext='.$context.')'; + $filter .= ')'; + + $attributes = array( + 'cn', + 'mail', + 'AstVoicemailMailbox', + 'AstVoicemailPassword', + 'AstVoicemailOptions', + 'AstVoicemailPager', + 'telephoneNumber', + 'AstExtension' + ); + + $search = ldap_search($this->_LDAP, $this->_params['basedn'], $filter, $attributes); + if ($search === false) { + throw new Shout_Exception("Unable to search directory: " . + ldap_error($this->_LDAP), ldap_errno($this->_LDAP)); + } + + $res = ldap_get_entries($this->_LDAP, $search); + if ($res === false) { + throw new Shout_Exception("Unable to fetch results from directory: " . + ldap_error($this->_LDAP), ldap_errno($this->_LDAP)); + } + + // ATTRIBUTES RETURNED FROM ldap_get_entries ARE ALL LOWER CASE!! + // It's a PHP thing. + $entries[$context] = array(); + $i = 0; + while ($i < $res['count']) { + list($extension) = explode('@', $res[$i]['astvoicemailmailbox'][0]); + $entries[$context][$extension] = array('extension' => $extension); + + $j = 0; + $entries[$context][$extension]['mailboxopts'] = array(); + if (empty($res[$i]['astvoicemailoptions']['count'])) { + $res[$i]['astvoicemailoptions']['count'] = -1; + } + while ($j < $res[$i]['astvoicemailoptions']['count']) { + $entries[$context][$extension]['mailboxopts'][] = + $res[$i]['astvoicemailoptions'][$j]; + $j++; + } + + $entries[$context][$extension]['mailboxpin'] = + $res[$i]['astvoicemailpassword'][0]; + + $entries[$context][$extension]['name'] = + $res[$i]['cn'][0]; + + $entries[$context][$extension]['email'] = + $res[$i]['mail'][0]; + + $entries[$context][$extension]['pageremail'] = + $res[$i]['astvoicemailpager'][0]; + + $j = 0; + $entries[$context][$extension]['numbers'] = array(); + if (empty($res[$i]['telephonenumber']['count'])) { + $res[$i]['telephonenumber']['count'] = -1; + } + while ($j < $res[$i]['telephonenumber']['count']) { + $entries[$context][$extension]['numbers'][] = + $res[$i]['telephonenumber'][$j]; + $j++; + } + + $j = 0; + $entries[$context][$extension]['devices'] = array(); + if (empty($res[$i]['astextension']['count'])) { + $res[$i]['astextension']['count'] = -1; + } + while ($j < $res[$i]['astextension']['count']) { + $entries[$context][$extension]['devices'][] = + $res[$i]['astextension'][$j]; + $j++; + } + + + $i++; + + } + + ksort($entries[$context]); + + return($entries[$context]); + } + + /** + * Get a list of destinations valid for this extension. + * A destination is either a telephone number, a VoIP device or an + * Instant Messaging address (a special case of VoIP). + * + * @param string $context Context for the extension + * @param string $extension Extension for which to return destinations + */ + function getDestinations($context, $extension) + { + // FIXME: LDAP filter injection + $filter = '(&(AstContext=%s)(AstVoicemailMailbox=%s))'; + $filter = sprintf($filter, $context, $extension); + + $attrs = array('telephoneNumber', 'AstExtensions'); + + $res = ldap_search($this->_LDAP, $this->_params['basedn'], + $filter, $attrs); + + if ($res === false) { + $msg = sprintf('Error while searching LDAP. Code %s; Message "%s"', + ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); + Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); + throw new Shout_Exception(_("Internal error searching the directory.")); + } + + $res = ldap_get_entries($this->_LDAP, $res); + + if ($res === false) { + $msg = sprintf('Error while searching LDAP. Code %s; Message "%s"', + ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); + Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); + throw new Shout_Exception(_("Internal error searching the directory.")); + } + + if ($res['count'] != 1) { + $msg = sprintf('Error while searching LDAP. Code %s; Message "%s"', + ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); + Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); + throw new Shout_Exception(_("Wrong number of entries found for this search.")); + } + + return array('numbers' => $res['telephonenumbers'], + 'devices' => $res['astextensions']); + } + + /** + * Get a context's dialplan and return as a multi-dimensional associative + * array + * + * @param string $context Context to return extensions for + * + * @param boolean $preprocess Parse includes and barelines and add their + * information into the extensions array + * + * @return array Multi-dimensional associative array of extensions data + * + */ + public function getDialplan($context, $preprocess = false) + { + # FIXME Implement preprocess functionality. Don't forget to cache! + static $dialplans = array(); + if (isset($dialplans[$context])) { + return $dialplans[$context]; + } + + $res = @ldap_search($this->_LDAP, + SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'], + "(&(objectClass=".SHOUT_CONTEXT_EXTENSIONS_OBJECTCLASS.")(context=$context))", + array(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE, SHOUT_DIALPLAN_INCLUDE_ATTRIBUTE, + SHOUT_DIALPLAN_IGNOREPAT_ATTRIBUTE, 'description', + SHOUT_DIALPLAN_BARELINE_ATTRIBUTE)); + if (!$res) { + return PEAR::raiseError("Unable to locate any extensions " . + "underneath ".SHOUT_ASTERISK_BRANCH.",".$this->_params['basedn'] . + " matching those search filters"); + } + + $res = ldap_get_entries($this->_LDAP, $res); + $dialplans[$context] = array(); + $dialplans[$context]['name'] = $context; + $i = 0; + while ($i < $res['count']) { + # Handle extension lines + if (isset($res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)])) { + $j = 0; + while ($j < $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)]['count']) { + @$line = $res[$i][strtolower(SHOUT_DIALPLAN_EXTENSIONLINE_ATTRIBUTE)][$j]; + + # Basic sanity check for length. FIXME + if (strlen($line) < 5) { + break; + } + # Can't use strtok here because there may be commass in the + # arg string + + # Get the extension + $token1 = strpos($line, ','); + $token2 = strpos($line, ',', $token1 + 1); + $token3 = strpos($line, '(', $token2 + 1); + + $extension = substr($line, 0, $token1); + if (!isset($dialplans[$context]['extensions'][$extension])) { + $dialplan[$context]['extensions'][$extension] = array(); + } + $token1++; + # Get the priority + $priority = substr($line, $token1, $token2 - $token1); + $dialplans[$context]['extensions'][$extension][$priority] = + array(); + $token2++; + + # Get Application and args + $application = substr($line, $token2, $token3 - $token2); + + if ($token3) { + $application = substr($line, $token2, $token3 - $token2); + $args = substr($line, $token3); + $args = preg_replace('/^\(/', '', $args); + $args = preg_replace('/\)$/', '', $args); + } else { + # This application must not have any args + $application = substr($line, $token2); + $args = ''; + } + + # Merge all that data into the returning array + $dialplans[$context]['extensions'][$extension][$priority]['application'] = + $application; + $dialplans[$context]['extensions'][$extension][$priority]['args'] = + $args; + $j++; + } + + # Sort the extensions data + foreach ($dialplans[$context]['extensions'] as + $extension => $data) { + ksort($dialplans[$context]['extensions'][$extension]); + } + uksort($dialplans[$context]['extensions'], + array(new Shout, "extensort")); + } + # Handle include lines + if (isset($res[$i]['asteriskincludeline'])) { + $j = 0; + while ($j < $res[$i]['asteriskincludeline']['count']) { + @$line = $res[$i]['asteriskincludeline'][$j]; + $dialplans[$context]['includes'][$j] = $line; + $j++; + } + } + + # Handle ignorepat + if (isset($res[$i]['asteriskignorepat'])) { + $j = 0; + while ($j < $res[$i]['asteriskignorepat']['count']) { + @$line = $res[$i]['asteriskignorepat'][$j]; + $dialplans[$context]['ignorepats'][$j] = $line; + $j++; + } + } + # Handle ignorepat + if (isset($res[$i]['asteriskextensionbareline'])) { + $j = 0; + while ($j < $res[$i]['asteriskextensionbareline']['count']) { + @$line = $res[$i]['asteriskextensionbareline'][$j]; + $dialplans[$context]['barelines'][$j] = $line; + $j++; + } + } + + # Increment object + $i++; + } + return $dialplans[$context]; + } + + /** + * Get the limits for the current user, the user's context, and global + * Return the most specific values in every case. Return default values + * where no data is found. If $extension is specified, $context must + * also be specified. + * + * @param optional string $context Context to search + * + * @param optional string $extension Extension/user to search + * + * @return array Array with elements indicating various limits + */ + # FIXME Figure out how this fits into Shout/Congregation better + public function getLimits($context = null, $extension = null) + { + + $limits = array('telephonenumbersmax', + 'voicemailboxesmax', + 'asteriskusersmax'); + + if(!is_null($extension) && is_null($context)) { + return PEAR::raiseError("Extension specified but no context " . + "given."); + } + + if (!is_null($context) && isset($limits[$context])) { + if (!is_null($extension) && + isset($limits[$context][$extension])) { + return $limits[$context][$extension]; + } + return $limits[$context]; + } + + # Set some default limits (to unlimited) + static $cachedlimits = array(); + # Initialize the limits with defaults + if (count($cachedlimits) < 1) { + foreach ($limits as $limit) { + $cachedlimits[$limit] = 99999; + } + } + + # Collect the global limits + $res = @ldap_search($this->_LDAP, + SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'], + '(&(objectClass=asteriskLimits)(cn=globals))', + $limits); + + if (!$res) { + return PEAR::raiseError('Unable to search the LDAP server for ' . + 'global limits'); + } + + $res = ldap_get_entries($this->_LDAP, $res); + # There should only have been one object returned so we'll just take the + # first result returned + if ($res['count'] > 0) { + foreach ($limits as $limit) { + if (isset($res[0][$limit][0])) { + $cachedlimits[$limit] = $res[0][$limit][0]; + } + } + } else { + return PEAR::raiseError("No global object found."); + } + + # Get limits for the context, if provided + if (isset($context)) { + $res = ldap_search($this->_LDAP, + SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'], + "(&(objectClass=asteriskLimits)(cn=$context))"); + + if (!$res) { + return PEAR::raiseError('Unable to search the LDAP server ' . + "for $context specific limits"); + } + + $cachedlimits[$context][$extension] = array(); + if ($res['count'] > 0) { + foreach ($limits as $limit) { + if (isset($res[0][$limit][0])) { + $cachedlimits[$context][$limit] = $res[0][$limit][0]; + } else { + # If no value is provided use the global limit + $cachedlimits[$context][$limit] = $cachedlimits[$limit]; + } + } + } else { + + foreach ($limits as $limit) { + $cachedlimits[$context][$limit] = + $cachedlimits[$limit]; + } + } + + if (isset($extension)) { + $res = @ldap_search($this->_LDAP, + SHOUT_USERS_BRANCH.','.$this->_params['basedn'], + "(&(objectClass=asteriskLimits)(voiceMailbox=$extension)". + "(context=$context))"); + + if (!$res) { + return PEAR::raiseError('Unable to search the LDAP server '. + "for Extension $extension, $context specific limits"); + } + + $cachedlimits[$context][$extension] = array(); + if ($res['count'] > 0) { + foreach ($limits as $limit) { + if (isset($res[0][$limit][0])) { + $cachedlimits[$context][$extension][$limit] = + $res[0][$limit][0]; + } else { + # If no value is provided use the context limit + $cachedlimits[$context][$extension][$limit] = + $cachedlimits[$context][$limit]; + } + } + } else { + foreach ($limits as $limit) { + $cachedlimits[$context][$extension][$limit] = + $cachedlimits[$context][$limit]; + } + } + return $cachedlimits[$context][$extension]; + } + return $cachedlimits[$context]; + } + } + + /** + * Save an extension to the LDAP tree + * + * @param string $context Context to which the user should be added + * + * @param string $extension Extension to be saved + * + * @param array $details Phone numbers, PIN, options, etc to be saved + * + * @return TRUE on success, PEAR::Error object on error + */ + public function saveExtension($context, $extension, $details) + { + $ldapKey = &$this->_ldapKey; + $appKey = &$this->_appKey; + # FIXME Access Control/Authorization + if (!Shout::checkRights("shout:contexts:$context:extensions", PERMS_EDIT, 1)) { + // FIXME: Allow users to edit themselves + //&& !($details[$appKey] == Auth::getAuth())) { + throw new Shout_Exception(_("Permission denied to save extensions in this context.")); + } + + $contexts = &$this->getContexts(); +// $domain = $contexts[$context]['domain']; + + # Check to ensure the extension is unique within this context + $filter = "(&(objectClass=AstVoicemailMailbox)(context=$context))"; + $reqattrs = array('dn', $ldapKey); + $res = @ldap_search($this->_LDAP, + SHOUT_USERS_BRANCH . ',' . $this->_params['basedn'], + $filter, $reqattrs); + if (!$res) { + return PEAR::raiseError('Unable to check directory for duplicate extension: ' . + ldap_error($this->_LDAP)); + } + if (($res['count'] > 1) || + ($res['count'] != 0 && + !in_array($res[0][$ldapKey], $details[$appKey]))) { + return PEAR::raiseError('Duplicate extension found. Not saving changes.'); + } + + $entry = array( + 'cn' => $details['name'], + 'sn' => $details['name'], + 'mail' => $details['email'], + 'uid' => $details['email'], + 'voiceMailbox' => $details['newextension'], + 'voiceMailboxPin' => $details['mailboxpin'], + 'context' => $context, + 'asteriskUserDialOptions' => $details['dialopts'], + ); + + if (!empty ($details['telephonenumber'])) { + $entry['telephoneNumber'] = $details['telephonenumber']; + } + + $validusers = &$this->getUsers($context); + if (!isset($validusers[$extension])) { + # Test to see if we're modifying an existing user that has + # no telephone system objectClasses and update that object/user + $rdn = $ldapKey.'='.$details[$appKey].','; + $branch = SHOUT_USERS_BRANCH.','.$this->_params['basedn']; + + # This test is something of a hack. I want a cheap way to check + # for the existance of an object. I don't want to do a full search + # so instead I compare that the dn equals the dn. If the object + # exists then it'll return true. If the object doesn't exist, + # it'll return error. If it ever returns false something wierd + # is going on. + $res = @ldap_compare($this->_LDAP, $rdn.$branch, + $ldapKey, $details[$appKey]); + if ($res === false) { + # We should never get here: a DN should ALWAYS match itself + return PEAR::raiseError("Internal Error: " . __FILE__ . " at " . + __LINE__); + } elseif ($res === true) { + # The object/user exists but doesn't have the Asterisk + # objectClasses + $extension = $details['newextension']; + + # $tmp is the minimal information required to establish + # an account in LDAP as required by the objectClasses. + # The entry will be fully populated below. + $tmp = array(); + $tmp['objectClass'] = array( + 'asteriskUser', + 'asteriskVoiceMailbox' + ); + $tmp['voiceMailbox'] = $extension; + $tmp['context'] = $context; + $res = @ldap_mod_replace($this->_LDAP, $rdn.$branch, $tmp); + if (!$res) { + return PEAR::raiseError("Unable to modify the user: " . + ldap_error($this->_LDAP)); + } + + # Populate the $validusers array to make the edit go smoothly + # below + $validusers[$extension] = array(); + $validusers[$extension][$appKey] = $details[$appKey]; + + # The remainder of the work is done at the outside of the + # parent if() like a normal edit. + + } elseif ($res === -1) { + # We must be adding a new user. + $entry['objectClass'] = array( + 'top', + 'person', + 'organizationalPerson', + 'inetOrgPerson', + 'hordePerson', + 'asteriskUser', + 'asteriskVoiceMailbox' + ); + + # Check to see if the maximum number of users for this context + # has been reached + $limits = $this->getLimits($context); + if (is_a($limits, "PEAR_Error")) { + return $limits; + } + if (count($validusers) >= $limits['asteriskusersmax']) { + return PEAR::raiseError('Maximum number of users reached.'); + } + + $res = @ldap_add($this->_LDAP, $rdn.$branch, $entry); + if (!$res) { + return PEAR::raiseError('LDAP Add failed: ' . + ldap_error($this->_LDAP)); + } + + return true; + } elseif (is_a($res, 'PEAR_Error')) { + # Some kind of internal error; not even sure if this is a + # possible outcome or not but I'll play it safe. + return $res; + } + } + + # Anything after this point is an edit. + + # Check to see if the object needs to be renamed (DN changed) + if ($validusers[$extension][$appKey] != $entry[$ldapKey]) { + $oldrdn = $ldapKey.'='.$validusers[$extension][$appKey]; + $oldparent = SHOUT_USERS_BRANCH.','.$this->_params['basedn']; + $newrdn = $ldapKey.'='.$entry[$ldapKey]; + $res = @ldap_rename($this->_LDAP, "$oldrdn,$oldparent", + $newrdn, $oldparent, true); + if (!$res) { + return PEAR::raiseError('LDAP Rename failed: ' . + ldap_error($this->_LDAP)); + } + } + + # Update the object/user + $dn = $ldapKey.'='.$entry[$ldapKey]; + $dn .= ','.SHOUT_USERS_BRANCH.','.$this->_params['basedn']; + $res = @ldap_modify($this->_LDAP, $dn, $entry); + if (!$res) { + return PEAR::raiseError('LDAP Modify failed: ' . + ldap_error($this->_LDAP)); + } + + # We must have been successful + return true; + } + + /** + * Deletes a user from the LDAP tree + * + * @param string $context Context to delete the user from + * @param string $extension Extension of the user to be deleted + * + * @return boolean True on success, PEAR::Error object on error + */ + public function deleteUser($context, $extension) + { + $ldapKey = &$this->_ldapKey; + $appKey = &$this->_appKey; + + if (!Shout::checkRights("shout:contexts:$context:users", + PERMS_DELETE, 1)) { + return PEAR::raiseError("No permission to delete users in this " . + "context."); + } + + $validusers = $this->getUsers($context); + if (!isset($validusers[$extension])) { + return PEAR::raiseError("That extension does not exist."); + } + + $dn = "$ldapKey=".$validusers[$extension][$appKey]; + $dn .= ',' . SHOUT_USERS_BRANCH . ',' . $this->_params['basedn']; + + $res = @ldap_delete($this->_LDAP, $dn); + if (!$res) { + return PEAR::raiseError("Unable to delete $extension from " . + "$context: " . ldap_error($this->_LDAP)); + } + return true; + } + + + /* Needed because uksort can't take a classed function as its callback arg */ + protected function _sortexten($e1, $e2) + { + print "$e1 and $e2\n"; + $ret = Shout::extensort($e1, $e2); + print "returning $ret"; + return $ret; + } + + /** + * Attempts to open a connection to the LDAP server. + * + * @return boolean True on success; exits (Horde::fatal()) on error. + * + * @access private + */ + protected function _connect() + { + if ($this->_connected) { + return; + } + + if (!Horde_Util::extensionExists('ldap')) { + throw new Horde_Exception('Required LDAP extension not found.'); + } + + Horde::assertDriverConfig($this->_params, $this->_params['class'], + array('hostspec', 'basedn', 'writedn')); + + /* Open an unbound connection to the LDAP server */ + $conn = ldap_connect($this->_params['hostspec'], $this->_params['port']); + if (!$conn) { + Horde::logMessage( + sprintf('Failed to open an LDAP connection to %s.', + $this->_params['hostspec']), + __FILE__, __LINE__, PEAR_LOG_ERR); + throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.'); + } + + /* Set hte LDAP protocol version. */ + if (isset($this->_params['version'])) { + $result = ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, + $this->_params['version']); + if ($result === false) { + Horde::logMessage( + sprintf('Set LDAP protocol version to %d failed: [%d] %s', + $this->_params['version'], + ldap_errno($conn), + ldap_error($conn)), + __FILE__, __LINE__, PEAR_LOG_WARNING); + throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn)); + } + } + + /* Start TLS if we're using it. */ + if (!empty($this->_params['tls'])) { + if (!@ldap_start_tls($conn)) { + Horde::logMessage( + sprintf('STARTTLS failed: [%d] %s', + @ldap_errno($this->_ds), + @ldap_error($this->_ds)), + __FILE__, __LINE__, PEAR_LOG_ERR); + } + } + + /* If necessary, bind to the LDAP server as the user with search + * permissions. */ + if (!empty($this->_params['searchdn'])) { + $bind = ldap_bind($conn, $this->_params['searchdn'], + $this->_params['searchpw']); + if ($bind === false) { + Horde::logMessage( + sprintf('Bind to server %s:%d with DN %s failed: [%d] %s', + $this->_params['hostspec'], + $this->_params['port'], + $this->_params['searchdn'], + @ldap_errno($conn), + @ldap_error($conn)), + __FILE__, __LINE__, PEAR_LOG_ERR); + throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn)); + } + } + + /* Store the connection handle at the instance level. */ + $this->_LDAP = $conn; + } + +} diff --git a/shout/lib/Driver/Sql.php b/shout/lib/Driver/Sql.php new file mode 100644 index 000000000..8eaac441b --- /dev/null +++ b/shout/lib/Driver/Sql.php @@ -0,0 +1,262 @@ +_connect(); + } + + public function getContexts() + { + $this->_connect(); + + $sql = 'SELECT context FROM %s'; + $sql = sprintf($sql, $this->_params['table']); + $vars = array(); + + $result = $this->_db->query($sql, $vars); + if ($result instanceof PEAR_Error) { + throw Shout_Exception($result); + } + + $row = $result->fetchRow(DB_FETCHMODE_ASSOC); + if ($row instanceof PEAR_Error) { + throw Shout_Exception($row); + } + + $contexts = array(); + while ($row && !($row instanceof PEAR_Error)) { + /* Add this new foo to the $_foo list. */ + $contexts[] = $row['context']; + + /* Advance to the new row in the result set. */ + $row = $result->fetchRow(DB_FETCHMODE_ASSOC); + } + + $result->free(); + return $contexts; + } + + /** + * Get a list of devices for a given context + * + * @param string $context Context in which to search for devicess + * + * @return array Array of devices within this context with their information + * + * @access private + */ + public function getDevices($context) + { + $sql = 'SELECT id, name, alias, callerid, context, mailbox, host, permit, ' . + 'nat, secret, disallow, allow FROM %s WHERE accountcode = ?'; + $sql = sprintf($sql, $this->_params['table']); + $args = array($context); + $result = $this->_db->query($sql, $args); + if ($result instanceof PEAR_Error) { + throw new Shout_Exception($result); + } + + $row = $result->fetchRow(DB_FETCHMODE_ASSOC); + if ($row instanceof PEAR_Error) { + throw new Shout_Exception($row); + } + + $devices = array(); + while ($row && !($row instanceof PEAR_Error)) { + // Asterisk uses the "name" field to indicate the registration + // identifier. We use the field "alias" to put a friendly name on + // the device. Thus devid -> name and name => alias + $devid = $row['name']; + $row['devid'] = $devid; + $row['name'] = $row['alias']; + unset($row['alias']); + + // Trim off the context from the mailbox number + list($row['mailbox']) = explode('@', $row['mailbox']); + + // Hide the DB internal ID from the front-end + unset($row['id']); + + $devices[$devid] = $row; + + /* Advance to the new row in the result set. */ + $row = $result->fetchRow(DB_FETCHMODE_ASSOC); + } + + $result->free(); + return $devices; + } + + /** + * Save a device (add or edit) to the backend. + * + * @param string $context The context in which this device is valid + * @param array $info Array of device details + */ + public function saveDevice($context, $info) + { + // See getDevices() for an explanation of these conversions + $info['alias'] = $info['name']; + $info['mailbox'] = $info['mailbox'] . '@' . $context; + + if ($info['devid']) { + // This is an edit + $info['name'] = $info['devid']; + $sql = 'UPDATE %s SET '; + } else { + // This is an add. Generate a new unique ID and secret + $devid = $context . uniqid(); + $secret = md5(uniqid(mt_rand)); + $sql = 'INSERT INTO %s (name, accountcode, callerid, mailbox, ' . + 'secret, alias, canreinvite, nat, type) ' . + ' VALUES (?, ?, ?, ?, ?, ?, "no", "yes", "peer")'; + + } + + + } + + /** + * Get a list of users valid for the contexts + * + * @param string $context Context on which to search + * + * @return array User information indexed by voice mailbox number + */ + public function getExtensions($context) + { + throw new Shout_Exception("Not implemented yet."); + } + + /** + * Save a user to the LDAP tree + * + * @param string $context Context to which the user should be added + * + * @param string $extension Extension to be saved + * + * @param array $userdetails Phone numbers, PIN, options, etc to be saved + * + * @return TRUE on success, PEAR::Error object on error + */ + public function saveExtension($context, $extension, $userdetails) + { + throw new Shout_Exception("Not implemented."); + } + + /** + * Deletes a user from the LDAP tree + * + * @param string $context Context to delete the user from + * @param string $extension Extension of the user to be deleted + * + * @return boolean True on success, PEAR::Error object on error + */ + public function deleteExtension($context, $extension) + { + throw new Shout_Exception("Not implemented."); + } + + /** + * Attempts to open a persistent connection to the SQL server. + * + * @throws Horde_Exception + */ + protected function _connect() + { + if ($this->_connected) { + return; + } + + Horde::assertDriverConfig($this->_params, $this->_params['class'], + array('phptype', 'charset', 'table')); + + if (!isset($this->_params['database'])) { + $this->_params['database'] = ''; + } + if (!isset($this->_params['username'])) { + $this->_params['username'] = ''; + } + if (!isset($this->_params['hostspec'])) { + $this->_params['hostspec'] = ''; + } + + /* Connect to the SQL server using the supplied parameters. */ + $this->_write_db = DB::connect($this->_params, + array('persistent' => !empty($this->_params['persistent']))); + if ($this->_write_db instanceof PEAR_Error) { + throw Horde_Exception($this->_write_db); + } + + // Set DB portability options. + switch ($this->_write_db->phptype) { + case 'mssql': + $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); + break; + + default: + $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } + + /* Check if we need to set up the read DB connection seperately. */ + if (!empty($this->_params['splitread'])) { + $params = array_merge($this->_params, $this->_params['read']); + $this->_db = DB::connect($params, + array('persistent' => !empty($params['persistent']))); + if ($this->_db instanceof PEAR_Error) { + throw Horde_Exception($this->_db); + } + + // Set DB portability options. + switch ($this->_db->phptype) { + case 'mssql': + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); + break; + + default: + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } + + } else { + /* Default to the same DB handle for the writer too. */ + $this->_db = $this->_write_db; + } + + $this->_connected = true; + } + + /** + * Disconnects from the SQL server and cleans up the connection. + */ + protected function _disconnect() + { + if ($this->_connected) { + $this->_connected = false; + $this->_db->disconnect(); + $this->_write_db->disconnect(); + } + } + +} diff --git a/shout/lib/Exception.php b/shout/lib/Exception.php new file mode 100644 index 000000000..301bcf7d8 --- /dev/null +++ b/shout/lib/Exception.php @@ -0,0 +1,4 @@ + + * + * See the enclosed file LICENSE for license information (GPL). If you + * did not receive this file, see http://www.horde.org/licenses/gpl.php. + * + * @package Shout + */ + +class DeviceDetailsForm extends Horde_Form { + + function __construct(&$vars) + { + global $shout_extensions; + + if ($vars->exists('devid')) { + $formtitle = "Edit Device"; + $devid = $vars->get('devid'); + $edit = true; + } else { + $formtitle = "Add Device"; + $edit = false; + } + + parent::__construct($vars, _("$formtitle - Context: $context")); + $this->addHidden('', 'action', 'text', true); + $vars->set('action', 'save'); + if ($edit) { + $this->addHidden('', 'devid', 'text', true); + + } + $this->addVariable(_("Device Name"), 'name', 'text', false); + $this->addVariable(_("Mailbox"), 'mailbox', 'int', false); + $this->addVariable(_("CallerID"), 'callerid', 'text', false); + + + return true; + } + +} \ No newline at end of file diff --git a/shout/lib/Forms/ExtensionForm.php b/shout/lib/Forms/ExtensionForm.php new file mode 100644 index 000000000..444319dda --- /dev/null +++ b/shout/lib/Forms/ExtensionForm.php @@ -0,0 +1,70 @@ + + * + * 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 $shout_extensions + * @param $vars + * @return + */ + function __construct(&$vars) + { + global $shout_extensions; + $context = $vars->get('context'); + if ($vars->exists('extension')) { + $formtitle = "Edit User"; + $extension = $vars->get('extension'); + } else { + $formtitle = "Add User"; + } + + parent::__construct($vars, _("$formtitle - Context: $context")); + $this->addHidden('', 'action', 'text', true); + $vars->set('action', 'save'); + $this->addHidden('', 'extension', 'int', true); + $vars->set('newextension', $extension); + $this->addVariable(_("Full Name"), 'name', 'text', true); + $this->addVariable(_("Extension"), 'newextension', 'int', true); + $this->addVariable(_("E-Mail Address"), 'email', 'email', true); + $this->addVariable(_("Pager E-Mail Address"), 'pageremail', 'email', false); + $this->addVariable(_("PIN"), 'mailboxpin', 'int', true); + + return true; + } + + /** + * Process this form, saving its information to the backend. + * + * @param string $context Context in which to execute this save + * FIXME: is there a better way to get the $context and $shout_extensions? + */ + function execute($context) + { + global $shout_extensions; + + $extension = $this->vars->get('extension'); + + # FIXME: Input Validation (Text::??) + $details = array( + 'newextension' => $vars->get('newextension'), + 'name' => $vars->get('name'), + 'mailboxpin' => $vars->get('mailboxpin'), + 'email' => $vars->get('email'), + ); + + $res = $shout_extensions->saveExtension($context, $extension, $details); + } + +} \ No newline at end of file diff --git a/shout/lib/Shout.php b/shout/lib/Shout.php new file mode 100644 index 000000000..8eed4aa8a --- /dev/null +++ b/shout/lib/Shout.php @@ -0,0 +1,136 @@ + + * + * 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 + * @version $Revision: 94 $ + * @since Shout 0.1 + * @package Shout + */ + +class Shout +{ + var $applist = array(); + var $_applist_curapp = ''; + var $_applist_curfield = ''; + + /** + * Build Shout's list of menu items. + * + * @access public + */ + static public function getMenu($returnType = 'object') + { + global $conf, $context, $section, $action; + + require_once 'Horde/Menu.php'; + + $menu = new Horde_Menu(HORDE_MENU_MASK_ALL); + + $menu->add(Horde::applicationUrl('extensions.php'), _("Extensions"), "user.png"); + $menu->add(Horde::applicationUrl('devices.php'), _("Devices"), "shout.png"); + $menu->add(Horde::applicationUrl('routes.php'), _("Call Paths")); + + + if ($returnType == 'object') { + return $menu; + } else { + return $menu->render(); + } + } + + /** + * Generate the tabs at the top of each Shout pages + * + * @param &$vars Reference to the passed in variables + * + * @return object Horde_UI_Tabs + */ + static public function getTabs($context, &$vars) + { + global $shout; + $perms = Horde_Perms::singleton(); + + $permprefix = 'shout:contexts:' . $context; + + $tabs = new Horde_UI_Tabs('section', $vars); + + if (Shout::checkRights($permprefix . ':extensions', null, 1)) { + $url = Horde::applicationUrl('extensions.php'); + $tabs->addTab(_("_Extensions"), $url, 'extensions'); + } + + if (Shout::checkRights($permprefix . ':dialplan', null, 1)) { + $url = Horde::applicationUrl('dialplan.php'); + $tabs->addTab(_("_Automated Attendant"), $url, 'dialplan'); + } + + if (Shout::checkRights($permprefix . ':conference', null, 1)) { + $url = Horde::applicationUrl('conference.php'); + $tabs->addTab(_("_Conference Rooms"), $url, 'conference'); + } + + if (Shout::checkRights($permprefix . ':moh', null, 1)) { + $url = Horde::applicationUrl('moh.php'); + $tabs->addTab(_("_Music on Hold"), $url, 'moh'); + } + + return $tabs; + } + + /** + * Checks for the given permissions for the current user on the given + * permission. Optionally check for higher-level permissions and ultimately + * test for superadmin priveleges. + * + * @param string $permname Name of the permission to check + * + * @param optional int $permmask Bitfield of permissions to check for + * + * @param options int $numparents Check for the same permissions this + * many levels up the tree + * + * @return boolean the effective permissions for the user. + */ + static public function checkRights($permname, $permmask = null, $numparents = 0) + { + if (Horde_Auth::isAdmin()) { return true; } + + $perms = Horde_Perms::singleton(); + if ($permmask === null) { + $permmask = PERMS_SHOW|PERMS_READ; + } + + # Default deny all permissions + $user = 0; + $superadmin = 0; + + $superadmin = $perms->hasPermission('shout:superadmin', + Horde_Auth::getAuth(), $permmask); + + while ($numparents >= 0) { + $tmpuser = $perms->hasPermission($permname, + Horde_Auth::getAuth(), $permmask); + + $user = $user | $tmpuser; + if ($numparents > 0) { + $pos = strrpos($permname, ':'); + if ($pos) { + $permname = substr($permname, 0, $pos); + } + } + $numparents--; + } + $test = $superadmin | $user; +$ret = ($test & $permmask) == $permmask; +print "Shout::checkRights() returning $ret"; + return ($test & $permmask) == $permmask; + } +} diff --git a/shout/lib/api.php b/shout/lib/api.php new file mode 100644 index 000000000..ce6d59f23 --- /dev/null +++ b/shout/lib/api.php @@ -0,0 +1,130 @@ + array(), + 'type' => '{urn:horde}stringArray', +); + +$_services['attributes'] = array( + 'args' => array(), + 'type' => '{urn:horde}stringArray', +); + +function _shout_perms() +{ + static $perms = array(); + if (!empty($perms)) { + return $perms; + } + + @define('SHOUT_BASE', dirname(__FILE__) . '/..'); + require_once SHOUT_BASE . '/lib/base.php'; + + $perms['tree']['shout']['superadmin'] = false; + $perms['title']['shout:superadmin'] = _("Super Administrator"); + + $contexts = $shout->getContexts(); + + $perms['tree']['shout']['contexts'] = false; + $perms['title']['shout:contexts'] = _("Contexts"); + + // Run through every contact source. + foreach ($contexts as $context => $contextInfo) { + $perms['tree']['shout']['contexts'][$context] = false; + $perms['title']['shout:contexts:' . $context] = $context; + + foreach( + array( + 'users' => 'Users', + 'dialplan' => 'Dialplan', + 'moh' => 'Music on Hold', + 'conferences' => 'Conferencing', + ) + as $module => $modname) { + $perms['tree']['shout']['contexts'][$context][$module] = false; + $perms['title']["shout:contexts:$context:$module"] = $modname; + } + } + +// function _shout_getContexts($searchfilters = SHOUT_CONTEXT_ALL, +// $filterperms = null) + + return $perms; +} + +function _shout_attributes() +{ + // See CONGREGATION_BASE/docs/api.txt for information on the structure + // of this array. + $shoutAttributes = array( + 'description' => 'Phone System User Settings', + 'attributes' => array( + 'extension' => array( + 'name' => 'Extension', + 'description' => 'Phone System Extension (doubles as Voice Mailbox Number', + 'type' => 'int', + 'size' => 3, + 'keys' => array( + 'ldap' => 'asteriskVoiceMailbox', + ), + 'limit' => 1, + 'required' => true, + 'infoset' => 'basic', + ), + + 'mailboxpin' => array( + 'name' => 'Mailbox PIN', + 'description' => 'Voice Mailbox PIN', + 'type' => 'int', + 'size' => 12, + 'keys' => array( + 'ldap' => 'asteriskVoiceMailboxPIN', + ), + 'limit' => 1, + 'required' => true, + 'infoset' => 'basic', + ), + + 'phonenumbers' => array( + 'name' => 'Telephone Numbers', + 'description' => 'Dialout phone numbers', + 'type' => 'cellphone', // WHY does Horde have cellphone but NOT + // telephone or just phonenumber??? + 'size' => 12, + 'keys' => array( + 'ldap' => 'telephoneNumber', + ), + 'limit' => 5, + 'required' => true, + 'infoset' => 'basic', + ), + + 'dialstring' => array( + 'name' => 'Dial String', + 'description' => 'Asterisk raw dial string', + 'type' => 'cellphone', // WHY does Horde have cellphone but NOT + // telephone or just phonenumber??? + 'size' => 12, + 'keys' => array( + 'ldap' => 'telephoneNumber', + ), + 'limit' => 5, + 'required' => true, + 'infoset' => 'restricted', + ), + ), + ); + + return $shoutAttributes; +} diff --git a/shout/lib/base.php b/shout/lib/base.php new file mode 100644 index 000000000..4ec1df0c2 --- /dev/null +++ b/shout/lib/base.php @@ -0,0 +1,87 @@ +pushApp('shout', array('check_perms' => true, 'logintasks' => true)); +} catch (Horde_Exception $e) { + Horde::authenticationFailureRedirect('shout', $e); +} + +$conf = &$GLOBALS['conf']; +@define('SHOUT_TEMPLATES', $registry->get('templates')); + +// Ensure Shout is properly configured before use +$shout_configured = (@is_readable(SHOUT_BASE . '/config/conf.php')); +if (!$shout_configured) { + Horde_Test::configFilesMissing('Shout', SHOUT_BASE, array('conf.php')); +} + +$notification = Horde_Notification::singleton(); +$notification->attach('status'); + +//// Shout base libraries. +//require_once SHOUT_BASE . '/lib/Shout.php'; +//require_once SHOUT_BASE . '/lib/Driver.php'; +// +//// Form libraries. +//require_once 'Horde/Form.php'; +//require_once 'Horde/Form/Renderer.php'; +// +//// Variable handling libraries +//require_once 'Horde/Variables.php'; +//require_once 'Horde/Text/Filter.php'; +// +//// UI classes. +//require_once 'Horde/UI/Tabs.php'; + +$shout_storage = Shout_Driver::factory('storage'); +$shout_extensions = Shout_Driver::factory('extensions'); +$shout_devices = Shout_Driver::factory('devices'); + +$context = Horde_Util::getFormData('context'); +$section = Horde_Util::getFormData('section'); + +try { + $contexts = $shout_storage->getContexts(); +} catch (Shout_Exception $e) { + $notification->push($e); + $contexts = false; +} + +if (count($contexts) == 1) { + // Default to the user's only context + $context = $contexts[0]; +} elseif (!empty($context) && !in_array($context, $contexts)) { + $notification->push('You do not have permission to access that context.', 'horde.error'); + $context = false; +} elseif (!empty($context)) { + $notification->push("Please select a context to continue.", 'horde.info'); + $context = false; +} + +$_SESSION['shout']['context'] = $context; \ No newline at end of file diff --git a/shout/lib/shoutAtts.php b/shout/lib/shoutAtts.php new file mode 100644 index 000000000..cc70a9094 --- /dev/null +++ b/shout/lib/shoutAtts.php @@ -0,0 +1,29 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + */ + +$shoutAttributes = array(); +$shoutAttributes['description'] = 'Phone System Options'; +$shoutAttributes['permsNode'] = 'shout:contexts:somethingOrOther'; +$shoutAttributes['attributes']['extension']['description'] = 'Internal Phone Extension'; +$shoutAttributes['attributes']['extension']['type'] = 'integer'; +$shoutAttributes['attributes']['extension']['size'] = 3; // max length for this particular string +$shoutAttributes['attributes']['extension']['ldapKey'] = 'voiceextensionsomethingIhavenoidea'; + +$shoutAttributes['attributes']['mailboxpin']['description'] = 'Mailbox PIN'; +$shoutAttributes['attributes']['mailboxpin']['type'] = 'integer'; +$shoutAttributes['attributes']['mailboxpin']['size'] = 4; +$shoutAttributes['attributes']['mailboxpin']['ldapKey'] = 'voicemailboxpin'; + +$shoutAttributes['attributes']['phonenumbers']['description'] = 'Phone Numbers'; +$shoutAttributes['attributes']['phonenumbers']['type'] = 'array'; +$shoutAttributes['attributes']['phonenumbers']['size'] = 1; +$shoutAttributes['attributes']['phonenumbers']['arrayType'] = 'string'; +$shoutAttributes['attributes']['phonenumbers']['ldapKey'] = 'asteriskuserphonenumbers'; + +?> diff --git a/shout/lib/version.php b/shout/lib/version.php new file mode 100644 index 000000000..b855f6a05 --- /dev/null +++ b/shout/lib/version.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/shout/moh.php b/shout/moh.php new file mode 100644 index 000000000..3c89ebd82 --- /dev/null +++ b/shout/moh.php @@ -0,0 +1,26 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + * + * @package shout + */ +if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) { + header('Location: /'); + exit(); +} + +$title = _('Music on Hold'); + +require SHOUT_TEMPLATES . '/common-header.inc'; +require SHOUT_TEMPLATES . '/menu.inc'; + +$notification->notify(); + +$tabs->render($section); + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/shout/security.php b/shout/security.php new file mode 100644 index 000000000..11f312697 --- /dev/null +++ b/shout/security.php @@ -0,0 +1,29 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + * + * @package shout + */ + +if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) { + header('Location: /'); + exit(); +} + +$title = _('Security'); + +require SHOUT_TEMPLATES . '/common-header.inc'; +require SHOUT_TEMPLATES . '/menu.inc'; + +$notification->notify(); + +$tabs->render($section); + +Shout::getApplist(); + +require $registry->get('templates', 'horde') . '/common-footer.inc'; \ No newline at end of file diff --git a/shout/templates/common-header.inc b/shout/templates/common-header.inc new file mode 100644 index 000000000..0d033523f --- /dev/null +++ b/shout/templates/common-header.inc @@ -0,0 +1,32 @@ + + + + + +' : '' ?> + + get('name'); + if (!empty($title)) $page_title .= ' :: ' . $title; + if (!empty($refresh_time) && !empty($refresh_url)) { + echo "\n"; + } + + Horde::includeScriptFiles(); + + ?> + <?php echo $page_title ?> + + + +> diff --git a/shout/templates/content_page b/shout/templates/content_page new file mode 100644 index 000000000..66fd553b8 --- /dev/null +++ b/shout/templates/content_page @@ -0,0 +1,8 @@ +
"; \ No newline at end of file diff --git a/shout/templates/devices/edit.inc b/shout/templates/devices/edit.inc new file mode 100644 index 000000000..a95682f37 --- /dev/null +++ b/shout/templates/devices/edit.inc @@ -0,0 +1,6 @@ +beginActive($Form->getTitle()); +$RENDERER->renderFormActive($Form, $vars); +$RENDERER->submit(); +$RENDERER->end(); +$Form->close($RENDERER); \ No newline at end of file diff --git a/shout/templates/devices/list.inc b/shout/templates/devices/list.inc new file mode 100644 index 000000000..49516c003 --- /dev/null +++ b/shout/templates/devices/list.inc @@ -0,0 +1,40 @@ +
+ Context: +
+ +
+ + + + + + + $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'); + ?> + + + + + + +
Device IDMailboxCallerID
+ + + + + +
+
diff --git a/shout/templates/dialplan/contexttree.inc b/shout/templates/dialplan/contexttree.inc new file mode 100644 index 000000000..dafbe7e87 --- /dev/null +++ b/shout/templates/dialplan/contexttree.inc @@ -0,0 +1,9 @@ +
+
Context:
+
+ +
+renderTree(true); ?> +
+Back to Top +
diff --git a/shout/templates/dialplan/dialplanlist.inc b/shout/templates/dialplan/dialplanlist.inc new file mode 100644 index 000000000..7ede9d86a --- /dev/null +++ b/shout/templates/dialplan/dialplanlist.inc @@ -0,0 +1,56 @@ + + + + + + + \ No newline at end of file diff --git a/shout/templates/dialplan/extensiondetail.inc b/shout/templates/dialplan/extensiondetail.inc new file mode 100644 index 000000000..a7ba902c4 --- /dev/null +++ b/shout/templates/dialplan/extensiondetail.inc @@ -0,0 +1,56 @@ + // --> + + +
Context:
 
+ + 0)) { + foreach ($dialplan['extensions'] as $extension => $priorities) { + $extname = Shout::exten2name($extension); + ?> + + + + + + + + $application) { + $rowcolor = $line % 2; + $line++; + ?> + + + + + + + +
+  edit +
PriorityApplication
 
+
+ $data) { + ?> + + + + + + + +
" + name=""> + + + - + " + name="" + onclick="javascript:dp.activatePriority('', '')"> + + + + " + name=""> + + + + " + name=""> + + + +
\ No newline at end of file diff --git a/shout/templates/dialplan/manager.inc b/shout/templates/dialplan/manager.inc new file mode 100644 index 000000000..6cf4e6cb7 --- /dev/null +++ b/shout/templates/dialplan/manager.inc @@ -0,0 +1,14 @@ +
+
Context:
+
+ +renderNavTree(); +$dpgui->generateAppList(); +$dpgui->renderExtensions(); +?> + \ No newline at end of file diff --git a/shout/templates/dialplan/priority-form-begin.inc b/shout/templates/dialplan/priority-form-begin.inc new file mode 100644 index 000000000..3efb74320 --- /dev/null +++ b/shout/templates/dialplan/priority-form-begin.inc @@ -0,0 +1,7 @@ +
+ + + + \ No newline at end of file diff --git a/shout/templates/dialplan/priority-form-end.inc b/shout/templates/dialplan/priority-form-end.inc new file mode 100644 index 000000000..55d12e0f5 --- /dev/null +++ b/shout/templates/dialplan/priority-form-end.inc @@ -0,0 +1,2 @@ +
+ Priorities +
+
\ No newline at end of file diff --git a/shout/templates/dialplan/priority-form-line.inc b/shout/templates/dialplan/priority-form-line.inc new file mode 100644 index 000000000..123df5af2 --- /dev/null +++ b/shout/templates/dialplan/priority-form-line.inc @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/shout/templates/extensions/edit.inc b/shout/templates/extensions/edit.inc new file mode 100644 index 000000000..a95682f37 --- /dev/null +++ b/shout/templates/extensions/edit.inc @@ -0,0 +1,6 @@ +beginActive($Form->getTitle()); +$RENDERER->renderFormActive($Form, $vars); +$RENDERER->submit(); +$RENDERER->end(); +$Form->close($RENDERER); \ No newline at end of file diff --git a/shout/templates/extensions/list.inc b/shout/templates/extensions/list.inc new file mode 100644 index 000000000..4b00d15aa --- /dev/null +++ b/shout/templates/extensions/list.inc @@ -0,0 +1,48 @@ +
+ Context: +
+ +
+ + + + + + + $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'); + ?> + + + + + + +
ExtensionNameE-Mail Address
+ + + + + + +
+
diff --git a/shout/templates/javascript/dialplan.js b/shout/templates/javascript/dialplan.js new file mode 100644 index 000000000..b0f06c3e6 --- /dev/null +++ b/shout/templates/javascript/dialplan.js @@ -0,0 +1,429 @@ +/** + * Shout Dialplan Javascript Class + * + * Provides the javascript class to create dynamic dialplans + * + * Copyright 2005 Ben Klang + * + * 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 + * @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 += '
\n'; + var priority = document.createElement('div'); + priority.className = 'priority'; + priority.id = 'priority-' + exten + '-' + p; + // The next 5 lines are hijinks required to make the highlighting work properly. We + // have to save a reference to this object in the pElement object so we can call back + // into the activate/deactivate routines. We also have to save the prio and exten because + // the onclick assignment has to be a function reference which takes no arguments. + // See above and below comments disparaging javascript. + priority.dp = this; + priority.exten = exten; + priority.prio = p; + priority.highlight = function () { this.dp.highlightPrio(this.exten, this.prio); } + priority.activate = function () { this.dp.activatePrio(this.exten, this.prio); } + priority.onclick = priority.highlight; + pList.appendChild(priority); + + var pButtons = document.createElement('span'); + pButtons.className = 'pButtons'; + pButtons.id = 'pButtons-' + exten + '-' + p; + priority.appendChild(pButtons); + + var button = document.createElement('span'); + button.className = 'add'; + button.id = 'pButton-add-'+exten+'-'+p; + button.dp = this; + button.exten = exten; + button.prio = p; + button.addPrio = function () { this.dp.addPrio(this.exten, this.prio); } + button.onclick = button.addPrio; + button.innerHTML='+'; + pButtons.appendChild(button); + + var button = document.createElement('span'); + button.className = 'remove'; + button.id = 'pButton-del-'+exten+'-'+p; + button.dp = this; + button.exten = exten; + button.prio = p; + button.delPrio = function () { this.dp.delPrio(this.exten, this.prio); } + button.onclick = button.delPrio; + button.innerHTML='-'; + pButtons.appendChild(button); + + var pElement = document.createElement('span'); + pElement.className = 'pElement'; + priority.appendChild(pElement); + + var pBox = document.createElement('span'); + pBox.className = 'pBox'; + pBox.id = 'pBox-'+exten+'-'+p; + pBox.style['display'] = 'inline'; + pBox.innerHTML = p; + pElement.appendChild(pBox); + + var pBoxInput = document.createElement('input'); + pBoxInput.type = 'text'; + pBoxInput.size = 3; + pBoxInput.id = 'pBoxInput-'+exten+'-'+p; + pBoxInput.name = 'pBoxInput-'+exten+'-'+p; + pBoxInput.value = p; + pBoxInput.maxlength = 3; + pBoxInput.style['display'] = 'none'; + pBoxInput.dp = this; + pBoxInput.exten = exten; + pBoxInput.prio = p; + pBoxInput.onblur = pBoxInput.deactivate; + pBoxInput.deactivate = function () { this.dp.deactivatePriority(); } + pElement.appendChild(pBoxInput); + + var pElement = document.createElement('span'); + pElement.className = 'pElement'; + priority.appendChild(pElement); + + var pApp = document.createElement('span'); + pApp.className = 'pApp'; + pApp.id = 'pApp-' + exten + '-' + p; + pApp.style['display'] = 'inline'; + pElement.appendChild(pApp); + pApp.innerHTML = this.dp[exten]['priorities'][p]['application']; + + var pAppInput = document.createElement('select'); + pAppInput.className = 'pAppInput'; + pAppInput.id = 'pAppInput-' + exten + '-' + p; + pAppInput.name = 'pAppInput-' + exten + '-' + p; + pAppInput.style['display'] ='none'; + pElement.appendChild(pAppInput); + this.genAppList(pAppInput, this.dp[exten]['priorities'][p]['application']); + + var pElement = document.createElement('span'); + pElement.className = 'pElement'; + priority.appendChild(pElement); + + var pArgs = document.createElement('span'); + pArgs.className = 'pArgs'; + pArgs.id = 'pArgs-' + exten + '-' + p; + pArgs.innerHTML = this.dp[exten]['priorities'][p]['args']; + pElement.appendChild(pArgs); + + var pArgsInput = document.createElement('input'); + pArgsInput.className = 'pArgsInput'; + pArgsInput.id = 'pArgsInput-' + exten + '-' + p; + pArgsInput.name = 'pArgsInput-' + exten + '-' + p; + pArgsInput.value = this.dp[exten]['priorities'][p]['args']; + pArgsInput.style['display'] = 'none'; + pElement.appendChild(pArgsInput); + } + return true; +} + +Dialplan.prototype.genAppList = function (selectObj, app) +{ + for (var a in this.applist) { + var o = document.createElement('option'); + o.value = this.applist[a]; + if (this.applist[a] == app) { + o.selected = true; + } + o.innerHTML = this.applist[a]; + selectObj.appendChild(o); + } + return true; +} + +Dialplan.prototype.addExten = function (exten, extenName) +{ + this.dp[exten] = new Array(); +} + +Dialplan.prototype.addPrio = function(exten, prio) +{ + prio = Number(prio); + if (this.dp[exten]['priorities'][prio] != 'undefined') { + // Due to javascript's inability to remove an array element while maintaining + // associations, we copy the elements into a tmp array and ultimately replace + // the object's copy. We will also have to sort the resulting array manually + // so it renders correctly. + var tmp = new Array(); + var plist = new Array(); + var i = 0; + var firstEmpty = prio + 1; + + // Locate an empty priority. We should not increment any priorities past this as + // the lower priorities will move to fill this hole. + while (this.dp[exten]['priorities'][firstEmpty]) { + firstEmpty++; + } + + for (p in this.dp[exten]['priorities']) { + p = Number(p); + // Make a notch for the new priority by incrementing all priorities greater + // than the requested one. Try to exclude error handling priorities + // which are unrelated to the changed extension. See README for + // more information. + // TODO: Make a decision about whether this is the best way to handle + // error handling priorities. + if (p > prio && (p < prio + 90 || p > prio + 100) && p < firstEmpty) { + tmp[p + 1] = this.dp[exten]['priorities'][p]; + plist[i++] = p + 1; + } else { + tmp[p] = this.dp[exten]['priorities'][p]; + plist[i++] = p; + } + } + + // Seed the new priority + p = prio + 1; + tmp[p] = new Array(); + tmp[p]['application'] = ''; + tmp[p]['args'] = ''; + plist[i] = p; + + + // Empty the original array + this.dp[exten]['priorities'] = new Array(); + + // Sort the priorities and put them back into the original array + plist.sort(this._numCompare); + for (i = 0; i < plist.length; i++) { + p = Number(plist[i]); + this.dp[exten]['priorities'][p] = tmp[p]; + } + } + + this.drawPrioTable(exten); + this.highlightPrio(exten, prio); + return true; +} + +Dialplan.prototype.insertPrio = function(exten, prio) +{ + // Simple wrapper for addPrio() + // Create an empty slot. Subtract one from the new prio because the + // behavior of addPrio is to append to the specified location. + return this.addPrio(exten, prio - 1); +} + +Dialplan.prototype.delPrio = function(exten, prio) +{ + prio = Number(prio); + if (this.dp[exten]['priorities'][prio] != 'undefined') { + // The .length method on this array always reports number of priorities + 1; + // Haven't yet solved this mystery but the below test does work correctly. + if (this.dp[exten]['priorities'].length <= 2) { + alert('Extensions must have at least one priority'); + return false; + } + // Due to javascript's inability to remove an array element while maintaining + // associations, we copy the elements into a tmp array and ultimately replace + // the object's copy. We will also have to sort the resulting array manually + // so it renders correctly. + var tmp = new Array(); + var plist = new Array(); + var i = 0; + var p; + + for (p in this.dp[exten]['priorities']) { + // Notch out the old priority by decrementing all priorities greater + // than the requested one. Try to exclude error handling priorities + // which are unrelated to the changed extension. See README for + // more information. + // TODO: Make a decision about whether this is the best way to handle + // error handling priorities. + p = Number(p); + if (p > prio && (p < prio + 90 || p > prio + 100)) { + tmp[p - 1] = this.dp[exten]['priorities'][p]; + plist[i++] = p - 1; + } else if (p != prio) { + tmp[p] = this.dp[exten]['priorities'][p]; + plist[i++] = p; + } + } + + // Empty the original array + this.dp[exten]['priorities'] = new Array(); + + // Sort the priorities and put them back into the original array + plist.sort(this._numCompare); + for (i = 0; i < plist.length; i++) { + p = Number(plist[i]); + this.dp[exten]['priorities'][p] = tmp[p]; + } + } + + this.curPrio = 0; + this.drawPrioTable(exten); + return true; +} + +Dialplan.prototype.renumberPrio = function(newPrio) +{ + // Copy the old prio to a temporary location for future use + var tmp = new Array(); + var oldPrio = Number(this.curPrio); + tmp = this.dp[this.curExten]['priorities'][oldPrio]; + newPrio = Number(newPrio); + + // Empty out the old priority + this.delPrio(this.curExten, oldPrio); + + this.insertPrio(this.curExten, newPrio); + // Copy the old priority into its new home + this.dp[this.curExten]['priorities'][newPrio] = tmp; + this.drawPrioTable(this.curExten); + + // Highlight the renumbered priority + this.curPrio = newPrio; + this.highlightPrio(this.curExten, this.curPrio); + + return true; +} + +Dialplan.prototype._numCompare = function(a, b) +{ + return (a - b); +} \ No newline at end of file diff --git a/shout/templates/menu.inc b/shout/templates/menu.inc new file mode 100644 index 000000000..070329a4a --- /dev/null +++ b/shout/templates/menu.inc @@ -0,0 +1,68 @@ + +getValue('widget_accesskey') ? + Horde::getAccessKey(_("Select _Context")) : ''; +$menu_view = $prefs->getValue('menu_view'); +?> + + diff --git a/shout/templates/table-limiter-begin.inc b/shout/templates/table-limiter-begin.inc new file mode 100644 index 000000000..91da0375f --- /dev/null +++ b/shout/templates/table-limiter-begin.inc @@ -0,0 +1 @@ + + +
\ No newline at end of file diff --git a/shout/templates/table-limiter-end.inc b/shout/templates/table-limiter-end.inc new file mode 100644 index 000000000..2c0bb586e --- /dev/null +++ b/shout/templates/table-limiter-end.inc @@ -0,0 +1,3 @@ +
\ No newline at end of file diff --git a/shout/themes/graphics/add-extension.gif b/shout/themes/graphics/add-extension.gif new file mode 100644 index 000000000..c343e4b8e Binary files /dev/null and b/shout/themes/graphics/add-extension.gif differ diff --git a/shout/themes/graphics/add-user.gif b/shout/themes/graphics/add-user.gif new file mode 100644 index 000000000..faac4f1ce Binary files /dev/null and b/shout/themes/graphics/add-user.gif differ diff --git a/shout/themes/graphics/shout.png b/shout/themes/graphics/shout.png new file mode 100644 index 000000000..e819577a0 Binary files /dev/null and b/shout/themes/graphics/shout.png differ diff --git a/shout/themes/graphics/telephone-pole.png b/shout/themes/graphics/telephone-pole.png new file mode 100644 index 000000000..7187c95c2 Binary files /dev/null and b/shout/themes/graphics/telephone-pole.png differ diff --git a/shout/themes/graphics/user.png b/shout/themes/graphics/user.png new file mode 100644 index 000000000..8b2c9946c Binary files /dev/null and b/shout/themes/graphics/user.png differ diff --git a/shout/themes/screen.css b/shout/themes/screen.css new file mode 100644 index 000000000..057a159c8 --- /dev/null +++ b/shout/themes/screen.css @@ -0,0 +1,189 @@ +.lighthint { + color: #fff; + font-size: 10px; + } + + +.darkhint { + color: #700; + font-size: 10px; + } + +table { + width: 100%; +} + +.header { + color: #fff; + background: #a22; + text-align: right; + font-style: italic; + font-weight: bold; + font-size: 14px; +} + +.userList{ + border: 1px solid #000; +} + +.uheader{ + background: #fff; + color: #a22; + text-align: left; + font-weight: bold; + font-size: 130%; + border-bottom: 1px dashed #000; +} + +#contextTree { + width: 180px; + left: 12px; + position: fixed; + top: 108px; + background: #fbb; + border: 1px solid #a22; + padding: 5px; + } + + +#extensionDetail { + left: 200px; + position: absolute; + top: 50px; + background: #ccc; + border: 1px solid #000; + padding: 15px; + } + +#extensionDetail .lightHint { + background: #888; + color: #ddd; +} + +#extensionDetail .darkHint{ + background: #797 + color: #fff; +} + +#extensionDetail .extension { + border: 2px solid #000; + background: inherit; + padding: 5px; + width: 450px; +} + +#extensionDetail .extensionHighlight { + border: 2px solid #a22; + padding: 5px; + padding-bottom: 20px; + background: #fbb; +} + +#extensionDetail .extensionBox { + background: #fff; + color: #a22; + font-weight: bold; + font-size: 14px; + border: 2px solid #787; + padding: 5px; +} + +#extensionDetail .extensionBoxHighlight { + background: #fbb; + color: #000; + font-weight: bold; + font-size: 14px; + border: 2px solid #fff; + padding: 5px; +} + +#extensionDetail .pList { + border: 1px solid #787; + background: inherit; + /*left: 10px; + position: relative;*/ +} + +#extensionDetail .pListHighlight{ + border: 1px solid #fff; + background: #fbb; + /*left: 10px; + position: relative;*/ +} + +#extensionDetail .priority { + padding: 4px; + /*left: 20px; + position: relative;*/ + background: inherit; + /*border: 1px solid #000;*/ +} +#extensionDetail .priorityHighlight { + background: #ccf; + padding: 3px; + border: 1px solid #000; + /*text-decoration: none; + font-color: #000;*/ +} + +#extensionDetail .pElement { + background: inherit; + padding: 3px; +} +#extensionDetail .pElementHighlight { + background: #ccf; + padding: 3px; + border-bottom: 1px solid #000; + border-top: 1px solid #000; + text-decoration: none; + font-color: #000; +} + +#extensionDetail .pBox { + background: #fff; + color: #a22; + padding: 3px; + border: 1px solid #000; + width: 120px; + height: 16px; + text-align: center; + vertical-align: middle; + text-decoration: underline; +} + +#extensionDetail .add { + border: 1px solid #000; + font-size: 10px; + font-weight: bold; + padding: 1px; + padding-left: 5px; + padding-right: 5px; +} + +#extensionDetail .remove { + border: 1px solid #000; + font-size: 10px; + font-weight: bold; + padding: 1px; + padding-left: 5px; + padding-right: 5px; +} + +#extensionDetail .pButtons { + visibility: hidden; + padding: 1px; +} + +#extensionDetail .pArgs { + border: 1px #777 dotted; + padding-left: 5px; + padding-right: 5px; +} + +/*#extensionDetail .pButtonsHighlight { + visibility: visible; + background: #ccf; + padding: 3px; + border-bottom: 1px solid #000; + border-top: 1px solid #000; +}*/ \ No newline at end of file diff --git a/templates/common-header.inc b/templates/common-header.inc deleted file mode 100644 index 0d033523f..000000000 --- a/templates/common-header.inc +++ /dev/null @@ -1,32 +0,0 @@ - - - - - -' : '' ?> - - get('name'); - if (!empty($title)) $page_title .= ' :: ' . $title; - if (!empty($refresh_time) && !empty($refresh_url)) { - echo "\n"; - } - - Horde::includeScriptFiles(); - - ?> - <?php echo $page_title ?> - - - -> diff --git a/templates/content_page b/templates/content_page deleted file mode 100644 index 66fd553b8..000000000 --- a/templates/content_page +++ /dev/null @@ -1,8 +0,0 @@ -
"; \ No newline at end of file diff --git a/templates/devices/edit.inc b/templates/devices/edit.inc deleted file mode 100644 index a95682f37..000000000 --- a/templates/devices/edit.inc +++ /dev/null @@ -1,6 +0,0 @@ -beginActive($Form->getTitle()); -$RENDERER->renderFormActive($Form, $vars); -$RENDERER->submit(); -$RENDERER->end(); -$Form->close($RENDERER); \ No newline at end of file diff --git a/templates/devices/list.inc b/templates/devices/list.inc deleted file mode 100644 index 49516c003..000000000 --- a/templates/devices/list.inc +++ /dev/null @@ -1,40 +0,0 @@ -
- Context: -
- -
- - - - - - - $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'); - ?> - - - - - - -
Device IDMailboxCallerID
- - - - - -
-
diff --git a/templates/dialplan/contexttree.inc b/templates/dialplan/contexttree.inc deleted file mode 100644 index dafbe7e87..000000000 --- a/templates/dialplan/contexttree.inc +++ /dev/null @@ -1,9 +0,0 @@ -
-
Context:
-
- -
-renderTree(true); ?> -
-Back to Top -
diff --git a/templates/dialplan/dialplanlist.inc b/templates/dialplan/dialplanlist.inc deleted file mode 100644 index 7ede9d86a..000000000 --- a/templates/dialplan/dialplanlist.inc +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/templates/dialplan/extensiondetail.inc b/templates/dialplan/extensiondetail.inc deleted file mode 100644 index a7ba902c4..000000000 --- a/templates/dialplan/extensiondetail.inc +++ /dev/null @@ -1,56 +0,0 @@ - // --> - - -
Context:
 
- - 0)) { - foreach ($dialplan['extensions'] as $extension => $priorities) { - $extname = Shout::exten2name($extension); - ?> - - - - - - - - $application) { - $rowcolor = $line % 2; - $line++; - ?> - - - - - - - -
-  edit -
PriorityApplication
 
-
- $data) { - ?> - - - - - - - -
" - name=""> - + - - - " - name="" - onclick="javascript:dp.activatePriority('', '')"> - - - - " - name=""> - - - - " - name=""> - - - -
\ No newline at end of file diff --git a/templates/dialplan/manager.inc b/templates/dialplan/manager.inc deleted file mode 100644 index 6cf4e6cb7..000000000 --- a/templates/dialplan/manager.inc +++ /dev/null @@ -1,14 +0,0 @@ -
-
Context:
-
- -renderNavTree(); -$dpgui->generateAppList(); -$dpgui->renderExtensions(); -?> - \ No newline at end of file diff --git a/templates/dialplan/priority-form-begin.inc b/templates/dialplan/priority-form-begin.inc deleted file mode 100644 index 3efb74320..000000000 --- a/templates/dialplan/priority-form-begin.inc +++ /dev/null @@ -1,7 +0,0 @@ -
- - - - \ No newline at end of file diff --git a/templates/dialplan/priority-form-end.inc b/templates/dialplan/priority-form-end.inc deleted file mode 100644 index 55d12e0f5..000000000 --- a/templates/dialplan/priority-form-end.inc +++ /dev/null @@ -1,2 +0,0 @@ -
- Priorities -
-
\ No newline at end of file diff --git a/templates/dialplan/priority-form-line.inc b/templates/dialplan/priority-form-line.inc deleted file mode 100644 index 123df5af2..000000000 --- a/templates/dialplan/priority-form-line.inc +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/templates/extensions/edit.inc b/templates/extensions/edit.inc deleted file mode 100644 index a95682f37..000000000 --- a/templates/extensions/edit.inc +++ /dev/null @@ -1,6 +0,0 @@ -beginActive($Form->getTitle()); -$RENDERER->renderFormActive($Form, $vars); -$RENDERER->submit(); -$RENDERER->end(); -$Form->close($RENDERER); \ No newline at end of file diff --git a/templates/extensions/list.inc b/templates/extensions/list.inc deleted file mode 100644 index 4b00d15aa..000000000 --- a/templates/extensions/list.inc +++ /dev/null @@ -1,48 +0,0 @@ -
- Context: -
- -
- - - - - - - $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'); - ?> - - - - - - -
ExtensionNameE-Mail Address
- - - - - - -
-
diff --git a/templates/javascript/dialplan.js b/templates/javascript/dialplan.js deleted file mode 100644 index b0f06c3e6..000000000 --- a/templates/javascript/dialplan.js +++ /dev/null @@ -1,429 +0,0 @@ -/** - * Shout Dialplan Javascript Class - * - * Provides the javascript class to create dynamic dialplans - * - * Copyright 2005 Ben Klang - * - * 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 - * @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 += '
\n'; - var priority = document.createElement('div'); - priority.className = 'priority'; - priority.id = 'priority-' + exten + '-' + p; - // The next 5 lines are hijinks required to make the highlighting work properly. We - // have to save a reference to this object in the pElement object so we can call back - // into the activate/deactivate routines. We also have to save the prio and exten because - // the onclick assignment has to be a function reference which takes no arguments. - // See above and below comments disparaging javascript. - priority.dp = this; - priority.exten = exten; - priority.prio = p; - priority.highlight = function () { this.dp.highlightPrio(this.exten, this.prio); } - priority.activate = function () { this.dp.activatePrio(this.exten, this.prio); } - priority.onclick = priority.highlight; - pList.appendChild(priority); - - var pButtons = document.createElement('span'); - pButtons.className = 'pButtons'; - pButtons.id = 'pButtons-' + exten + '-' + p; - priority.appendChild(pButtons); - - var button = document.createElement('span'); - button.className = 'add'; - button.id = 'pButton-add-'+exten+'-'+p; - button.dp = this; - button.exten = exten; - button.prio = p; - button.addPrio = function () { this.dp.addPrio(this.exten, this.prio); } - button.onclick = button.addPrio; - button.innerHTML='+'; - pButtons.appendChild(button); - - var button = document.createElement('span'); - button.className = 'remove'; - button.id = 'pButton-del-'+exten+'-'+p; - button.dp = this; - button.exten = exten; - button.prio = p; - button.delPrio = function () { this.dp.delPrio(this.exten, this.prio); } - button.onclick = button.delPrio; - button.innerHTML='-'; - pButtons.appendChild(button); - - var pElement = document.createElement('span'); - pElement.className = 'pElement'; - priority.appendChild(pElement); - - var pBox = document.createElement('span'); - pBox.className = 'pBox'; - pBox.id = 'pBox-'+exten+'-'+p; - pBox.style['display'] = 'inline'; - pBox.innerHTML = p; - pElement.appendChild(pBox); - - var pBoxInput = document.createElement('input'); - pBoxInput.type = 'text'; - pBoxInput.size = 3; - pBoxInput.id = 'pBoxInput-'+exten+'-'+p; - pBoxInput.name = 'pBoxInput-'+exten+'-'+p; - pBoxInput.value = p; - pBoxInput.maxlength = 3; - pBoxInput.style['display'] = 'none'; - pBoxInput.dp = this; - pBoxInput.exten = exten; - pBoxInput.prio = p; - pBoxInput.onblur = pBoxInput.deactivate; - pBoxInput.deactivate = function () { this.dp.deactivatePriority(); } - pElement.appendChild(pBoxInput); - - var pElement = document.createElement('span'); - pElement.className = 'pElement'; - priority.appendChild(pElement); - - var pApp = document.createElement('span'); - pApp.className = 'pApp'; - pApp.id = 'pApp-' + exten + '-' + p; - pApp.style['display'] = 'inline'; - pElement.appendChild(pApp); - pApp.innerHTML = this.dp[exten]['priorities'][p]['application']; - - var pAppInput = document.createElement('select'); - pAppInput.className = 'pAppInput'; - pAppInput.id = 'pAppInput-' + exten + '-' + p; - pAppInput.name = 'pAppInput-' + exten + '-' + p; - pAppInput.style['display'] ='none'; - pElement.appendChild(pAppInput); - this.genAppList(pAppInput, this.dp[exten]['priorities'][p]['application']); - - var pElement = document.createElement('span'); - pElement.className = 'pElement'; - priority.appendChild(pElement); - - var pArgs = document.createElement('span'); - pArgs.className = 'pArgs'; - pArgs.id = 'pArgs-' + exten + '-' + p; - pArgs.innerHTML = this.dp[exten]['priorities'][p]['args']; - pElement.appendChild(pArgs); - - var pArgsInput = document.createElement('input'); - pArgsInput.className = 'pArgsInput'; - pArgsInput.id = 'pArgsInput-' + exten + '-' + p; - pArgsInput.name = 'pArgsInput-' + exten + '-' + p; - pArgsInput.value = this.dp[exten]['priorities'][p]['args']; - pArgsInput.style['display'] = 'none'; - pElement.appendChild(pArgsInput); - } - return true; -} - -Dialplan.prototype.genAppList = function (selectObj, app) -{ - for (var a in this.applist) { - var o = document.createElement('option'); - o.value = this.applist[a]; - if (this.applist[a] == app) { - o.selected = true; - } - o.innerHTML = this.applist[a]; - selectObj.appendChild(o); - } - return true; -} - -Dialplan.prototype.addExten = function (exten, extenName) -{ - this.dp[exten] = new Array(); -} - -Dialplan.prototype.addPrio = function(exten, prio) -{ - prio = Number(prio); - if (this.dp[exten]['priorities'][prio] != 'undefined') { - // Due to javascript's inability to remove an array element while maintaining - // associations, we copy the elements into a tmp array and ultimately replace - // the object's copy. We will also have to sort the resulting array manually - // so it renders correctly. - var tmp = new Array(); - var plist = new Array(); - var i = 0; - var firstEmpty = prio + 1; - - // Locate an empty priority. We should not increment any priorities past this as - // the lower priorities will move to fill this hole. - while (this.dp[exten]['priorities'][firstEmpty]) { - firstEmpty++; - } - - for (p in this.dp[exten]['priorities']) { - p = Number(p); - // Make a notch for the new priority by incrementing all priorities greater - // than the requested one. Try to exclude error handling priorities - // which are unrelated to the changed extension. See README for - // more information. - // TODO: Make a decision about whether this is the best way to handle - // error handling priorities. - if (p > prio && (p < prio + 90 || p > prio + 100) && p < firstEmpty) { - tmp[p + 1] = this.dp[exten]['priorities'][p]; - plist[i++] = p + 1; - } else { - tmp[p] = this.dp[exten]['priorities'][p]; - plist[i++] = p; - } - } - - // Seed the new priority - p = prio + 1; - tmp[p] = new Array(); - tmp[p]['application'] = ''; - tmp[p]['args'] = ''; - plist[i] = p; - - - // Empty the original array - this.dp[exten]['priorities'] = new Array(); - - // Sort the priorities and put them back into the original array - plist.sort(this._numCompare); - for (i = 0; i < plist.length; i++) { - p = Number(plist[i]); - this.dp[exten]['priorities'][p] = tmp[p]; - } - } - - this.drawPrioTable(exten); - this.highlightPrio(exten, prio); - return true; -} - -Dialplan.prototype.insertPrio = function(exten, prio) -{ - // Simple wrapper for addPrio() - // Create an empty slot. Subtract one from the new prio because the - // behavior of addPrio is to append to the specified location. - return this.addPrio(exten, prio - 1); -} - -Dialplan.prototype.delPrio = function(exten, prio) -{ - prio = Number(prio); - if (this.dp[exten]['priorities'][prio] != 'undefined') { - // The .length method on this array always reports number of priorities + 1; - // Haven't yet solved this mystery but the below test does work correctly. - if (this.dp[exten]['priorities'].length <= 2) { - alert('Extensions must have at least one priority'); - return false; - } - // Due to javascript's inability to remove an array element while maintaining - // associations, we copy the elements into a tmp array and ultimately replace - // the object's copy. We will also have to sort the resulting array manually - // so it renders correctly. - var tmp = new Array(); - var plist = new Array(); - var i = 0; - var p; - - for (p in this.dp[exten]['priorities']) { - // Notch out the old priority by decrementing all priorities greater - // than the requested one. Try to exclude error handling priorities - // which are unrelated to the changed extension. See README for - // more information. - // TODO: Make a decision about whether this is the best way to handle - // error handling priorities. - p = Number(p); - if (p > prio && (p < prio + 90 || p > prio + 100)) { - tmp[p - 1] = this.dp[exten]['priorities'][p]; - plist[i++] = p - 1; - } else if (p != prio) { - tmp[p] = this.dp[exten]['priorities'][p]; - plist[i++] = p; - } - } - - // Empty the original array - this.dp[exten]['priorities'] = new Array(); - - // Sort the priorities and put them back into the original array - plist.sort(this._numCompare); - for (i = 0; i < plist.length; i++) { - p = Number(plist[i]); - this.dp[exten]['priorities'][p] = tmp[p]; - } - } - - this.curPrio = 0; - this.drawPrioTable(exten); - return true; -} - -Dialplan.prototype.renumberPrio = function(newPrio) -{ - // Copy the old prio to a temporary location for future use - var tmp = new Array(); - var oldPrio = Number(this.curPrio); - tmp = this.dp[this.curExten]['priorities'][oldPrio]; - newPrio = Number(newPrio); - - // Empty out the old priority - this.delPrio(this.curExten, oldPrio); - - this.insertPrio(this.curExten, newPrio); - // Copy the old priority into its new home - this.dp[this.curExten]['priorities'][newPrio] = tmp; - this.drawPrioTable(this.curExten); - - // Highlight the renumbered priority - this.curPrio = newPrio; - this.highlightPrio(this.curExten, this.curPrio); - - return true; -} - -Dialplan.prototype._numCompare = function(a, b) -{ - return (a - b); -} \ No newline at end of file diff --git a/templates/menu.inc b/templates/menu.inc deleted file mode 100644 index 070329a4a..000000000 --- a/templates/menu.inc +++ /dev/null @@ -1,68 +0,0 @@ - -getValue('widget_accesskey') ? - Horde::getAccessKey(_("Select _Context")) : ''; -$menu_view = $prefs->getValue('menu_view'); -?> - - diff --git a/templates/table-limiter-begin.inc b/templates/table-limiter-begin.inc deleted file mode 100644 index 91da0375f..000000000 --- a/templates/table-limiter-begin.inc +++ /dev/null @@ -1 +0,0 @@ - - -
\ No newline at end of file diff --git a/templates/table-limiter-end.inc b/templates/table-limiter-end.inc deleted file mode 100644 index 2c0bb586e..000000000 --- a/templates/table-limiter-end.inc +++ /dev/null @@ -1,3 +0,0 @@ -
\ No newline at end of file diff --git a/themes/graphics/add-extension.gif b/themes/graphics/add-extension.gif deleted file mode 100644 index c343e4b8e..000000000 Binary files a/themes/graphics/add-extension.gif and /dev/null differ diff --git a/themes/graphics/add-user.gif b/themes/graphics/add-user.gif deleted file mode 100644 index faac4f1ce..000000000 Binary files a/themes/graphics/add-user.gif and /dev/null differ diff --git a/themes/graphics/shout.png b/themes/graphics/shout.png deleted file mode 100644 index e819577a0..000000000 Binary files a/themes/graphics/shout.png and /dev/null differ diff --git a/themes/graphics/telephone-pole.png b/themes/graphics/telephone-pole.png deleted file mode 100644 index 7187c95c2..000000000 Binary files a/themes/graphics/telephone-pole.png and /dev/null differ diff --git a/themes/graphics/user.png b/themes/graphics/user.png deleted file mode 100644 index 8b2c9946c..000000000 Binary files a/themes/graphics/user.png and /dev/null differ diff --git a/themes/screen.css b/themes/screen.css deleted file mode 100644 index 057a159c8..000000000 --- a/themes/screen.css +++ /dev/null @@ -1,189 +0,0 @@ -.lighthint { - color: #fff; - font-size: 10px; - } - - -.darkhint { - color: #700; - font-size: 10px; - } - -table { - width: 100%; -} - -.header { - color: #fff; - background: #a22; - text-align: right; - font-style: italic; - font-weight: bold; - font-size: 14px; -} - -.userList{ - border: 1px solid #000; -} - -.uheader{ - background: #fff; - color: #a22; - text-align: left; - font-weight: bold; - font-size: 130%; - border-bottom: 1px dashed #000; -} - -#contextTree { - width: 180px; - left: 12px; - position: fixed; - top: 108px; - background: #fbb; - border: 1px solid #a22; - padding: 5px; - } - - -#extensionDetail { - left: 200px; - position: absolute; - top: 50px; - background: #ccc; - border: 1px solid #000; - padding: 15px; - } - -#extensionDetail .lightHint { - background: #888; - color: #ddd; -} - -#extensionDetail .darkHint{ - background: #797 - color: #fff; -} - -#extensionDetail .extension { - border: 2px solid #000; - background: inherit; - padding: 5px; - width: 450px; -} - -#extensionDetail .extensionHighlight { - border: 2px solid #a22; - padding: 5px; - padding-bottom: 20px; - background: #fbb; -} - -#extensionDetail .extensionBox { - background: #fff; - color: #a22; - font-weight: bold; - font-size: 14px; - border: 2px solid #787; - padding: 5px; -} - -#extensionDetail .extensionBoxHighlight { - background: #fbb; - color: #000; - font-weight: bold; - font-size: 14px; - border: 2px solid #fff; - padding: 5px; -} - -#extensionDetail .pList { - border: 1px solid #787; - background: inherit; - /*left: 10px; - position: relative;*/ -} - -#extensionDetail .pListHighlight{ - border: 1px solid #fff; - background: #fbb; - /*left: 10px; - position: relative;*/ -} - -#extensionDetail .priority { - padding: 4px; - /*left: 20px; - position: relative;*/ - background: inherit; - /*border: 1px solid #000;*/ -} -#extensionDetail .priorityHighlight { - background: #ccf; - padding: 3px; - border: 1px solid #000; - /*text-decoration: none; - font-color: #000;*/ -} - -#extensionDetail .pElement { - background: inherit; - padding: 3px; -} -#extensionDetail .pElementHighlight { - background: #ccf; - padding: 3px; - border-bottom: 1px solid #000; - border-top: 1px solid #000; - text-decoration: none; - font-color: #000; -} - -#extensionDetail .pBox { - background: #fff; - color: #a22; - padding: 3px; - border: 1px solid #000; - width: 120px; - height: 16px; - text-align: center; - vertical-align: middle; - text-decoration: underline; -} - -#extensionDetail .add { - border: 1px solid #000; - font-size: 10px; - font-weight: bold; - padding: 1px; - padding-left: 5px; - padding-right: 5px; -} - -#extensionDetail .remove { - border: 1px solid #000; - font-size: 10px; - font-weight: bold; - padding: 1px; - padding-left: 5px; - padding-right: 5px; -} - -#extensionDetail .pButtons { - visibility: hidden; - padding: 1px; -} - -#extensionDetail .pArgs { - border: 1px #777 dotted; - padding-left: 5px; - padding-right: 5px; -} - -/*#extensionDetail .pButtonsHighlight { - visibility: visible; - background: #ccf; - padding: 3px; - border-bottom: 1px solid #000; - border-top: 1px solid #000; -}*/ \ No newline at end of file