Import Beatnik from CVS
authorBen Klang <ben@alkaloid.net>
Wed, 16 Sep 2009 16:16:18 +0000 (12:16 -0400)
committerBen Klang <ben@alkaloid.net>
Wed, 16 Sep 2009 16:16:18 +0000 (12:16 -0400)
46 files changed:
beatnik/autogenerate.php [new file with mode: 0644]
beatnik/commit.php [new file with mode: 0644]
beatnik/config/.cvsignore [new file with mode: 0644]
beatnik/config/autogenerate.php.dist [new file with mode: 0644]
beatnik/config/conf.xml [new file with mode: 0644]
beatnik/config/prefs.php.dist [new file with mode: 0644]
beatnik/delete.php [new file with mode: 0644]
beatnik/docs/CHANGES [new file with mode: 0644]
beatnik/docs/CREDITS [new file with mode: 0644]
beatnik/docs/INSTALL [new file with mode: 0644]
beatnik/docs/RELEASE_NOTES [new file with mode: 0644]
beatnik/docs/TODO [new file with mode: 0644]
beatnik/editrec.php [new file with mode: 0644]
beatnik/index.php [new file with mode: 0644]
beatnik/js/beatnik.js [new file with mode: 0644]
beatnik/js/src/beatnik.js [new file with mode: 0644]
beatnik/lib/Application.php [new file with mode: 0644]
beatnik/lib/Beatnik.php [new file with mode: 0644]
beatnik/lib/Driver.php [new file with mode: 0644]
beatnik/lib/Driver/ldap2dns.php [new file with mode: 0644]
beatnik/lib/Driver/pdnsgsql.php [new file with mode: 0644]
beatnik/lib/Driver/sql.php [new file with mode: 0644]
beatnik/lib/Forms/Autogenerate.php [new file with mode: 0644]
beatnik/lib/Forms/DeleteRecord.php [new file with mode: 0644]
beatnik/lib/Forms/EditRecord.php [new file with mode: 0644]
beatnik/lib/base.php [new file with mode: 0644]
beatnik/lib/version.php [new file with mode: 0644]
beatnik/listzones.php [new file with mode: 0644]
beatnik/locale/en_US/help.xml [new file with mode: 0644]
beatnik/locale/sl_SI/LC_MESSAGES/beatnik.mo [new file with mode: 0644]
beatnik/po/beatnik.pot [new file with mode: 0644]
beatnik/po/sl_SI.po [new file with mode: 0644]
beatnik/scripts/export_config.php [new file with mode: 0644]
beatnik/scripts/sql/beatnik.mysql.php [new file with mode: 0644]
beatnik/templates/common-header.inc [new file with mode: 0644]
beatnik/templates/listzones/footer.inc [new file with mode: 0644]
beatnik/templates/listzones/header.inc [new file with mode: 0644]
beatnik/templates/listzones/row.inc [new file with mode: 0644]
beatnik/templates/menu.inc [new file with mode: 0644]
beatnik/templates/view/footer.inc [new file with mode: 0644]
beatnik/templates/view/header.inc [new file with mode: 0644]
beatnik/templates/view/record.inc [new file with mode: 0644]
beatnik/themes/graphics/beatnik.png [new file with mode: 0644]
beatnik/themes/graphics/commit-all.png [new file with mode: 0644]
beatnik/themes/screen.css [new file with mode: 0644]
beatnik/viewzone.php [new file with mode: 0644]

diff --git a/beatnik/autogenerate.php b/beatnik/autogenerate.php
new file mode 100644 (file)
index 0000000..8e2deed
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Delete records
+ *
+ * Copyright 2006-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (BSD). If you
+ * did not receive this file, see http://cvs.horde.org/co.php/merk/LICENSE.
+ *
+ * $Horde: beatnik/autogenerate.php,v 1.16 2009/07/14 00:25:28 mrubinsk Exp $
+ *
+ * @author Duck <duck@obala.net>
+ */
+
+define('BEATNIK_BASE', dirname(__FILE__));
+require_once BEATNIK_BASE . '/lib/base.php';
+require_once BEATNIK_BASE . '/lib/Forms/Autogenerate.php';
+
+$viewurl = Horde::applicationUrl('viewzone.php');
+
+$vars = Horde_Variables::getDefaultVariables();
+$form = new Autogenerate($vars);
+
+if ($form->validate($vars)) {
+    if (Horde_Util::getFormData('submitbutton') == _("Autogenerate")) {
+       $result = Beatnik::autogenerate($vars);
+       if (is_a($result, 'PEAR_Error')) {
+           $notification->push($zonedata, 'horde.error');
+           header('Location:' . Horde::applicationUrl('listzones.php'));
+           exit;
+       }
+    } else {
+        $notification->push(_("Autogeneration not performed"), 'horde.warning');
+    }
+
+    header('Location: ' . $viewurl);
+    exit;
+}
+
+$title = _("Autogenerate");
+require BEATNIK_BASE . '/templates/common-header.inc';
+require BEATNIK_BASE . '/templates/menu.inc';
+
+$form->renderActive(null, null, Horde::applicationUrl('autogenerate.php'), 'post');
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/beatnik/commit.php b/beatnik/commit.php
new file mode 100644 (file)
index 0000000..5aadd12
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/**
+ * $Horde: beatnik/commit.php,v 1.11 2009/07/03 10:05:29 duck Exp $
+ *
+ * Copyright 2006-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+define('BEATNIK_BASE', dirname(__FILE__));
+require_once BEATNIK_BASE . '/lib/base.php';
+require_once BEATNIK_BASE . '/lib/Forms/EditRecord.php';
+
+$domains = array();
+if (Horde_Util::getGet('domain') == 'current') {
+    $url = Horde::applicationUrl('viewzone.php');
+    $domains[] = $_SESSION['beatnik']['curdomain'];
+} elseif (Horde_Util::getGet('domain') == 'all') {
+    $url = Horde::applicationUrl('listzones.php');
+    foreach (Beatnik::needCommit() as $domain) {
+        $domains[] = $beatnik_driver->getDomain($domain);
+    }
+}
+
+foreach ($domains as $domain) {
+    $_SESSION['beatnik']['curdomain'] = $domain;
+    $vars = new Horde_Variables;
+
+    $vars->set('rectype', 'soa');
+    foreach ($domain as $field => $value) {
+        $vars->set($field, $value);
+    }
+    $vars->set('serial', Beatnik::incrementSerial($domain['serial']));
+
+    $form = new EditRecord($vars);
+    $form->useToken(false);
+    $form->setSubmitted(true);
+    if ($form->validate($vars)) {
+        $form->getInfo($vars, $info);
+        $result = $beatnik_driver->saveRecord($info);
+
+        if (is_a($result, 'PEAR_Error')) {
+            $notification->push($result->getMessage() . ': ' . $result->getDebugInfo(), 'horde.error');
+        } else {
+            $notification->push(sprintf(_('Zone serial for %s incremented.'), $domain['zonename']), 'horde.success');
+        }
+    } else {
+        $notification->push(sprintf(_("Unable to construct valid SOA for %s.  Not incrementing serial."), $domain['zonename']), 'horde.error');
+    }
+}
+
+header('Location: ' . $url);
+exit;
diff --git a/beatnik/config/.cvsignore b/beatnik/config/.cvsignore
new file mode 100644 (file)
index 0000000..841867b
--- /dev/null
@@ -0,0 +1,4 @@
+autogenerate.php
+conf.php
+conf.bak.php
+prefs.php
diff --git a/beatnik/config/autogenerate.php.dist b/beatnik/config/autogenerate.php.dist
new file mode 100644 (file)
index 0000000..7704261
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Autogenerate template
+ *
+ * $Horde: beatnik/config/autogenerate.php.dist,v 1.5 2007/06/27 17:23:28 jan Exp $
+ *
+ * Copyright 2006-2007 Duck <duck@obala.net>
+ *
+ * NOTE: Template is an multidimensional array. 
+ *       The first level key define record types
+ *       For each type you can specify the replacement behavior and how
+ *       autogenerate will treat old records.  'all' deletes all records
+ *       of the same type, 'match' deletes those which have the same hostname,
+ *       and 'none' simply adds more records leaving everything existing in
+ *       place.
+ *       The 'records' element is an array of arrays of Beatnick::getRecFields()
+ *       key => value pairs.
+ *
+ * EXAMPLE: 
+ *
+ *      $template['cname'][] = array('hostname' => 'www',
+ *                                   'pointer' => 'server1',
+ *                                   'ttl' => 3600);
+ *      $template['mx'][] = array('pointer' => 'server2',
+ *                                'pref' => 10);
+ *
+ * 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 Duck <duck@obala.net>
+ * @package Beatnik
+ */
+$templates['example'] = array(
+    'description' => _("Example Template"),
+    'types' => array(
+
+        // Example 'NS' records
+        'ns' => array(
+            'replace' => 'all', // Set to 'all' to remove all pre-existing
+                               // NS records
+
+            'records' => array(      // Array of records to be created
+                array('hostname' => 'ns1', 'pointer' => '10.0.0.1' ),
+                array('hostname' => 'ns2', 'pointer' => '10.0.0.2' ),
+            ),
+        ),
+
+        // Example 'MX' record
+        'mx' => array(
+            'replace' => 'none', // Set to 'none' to leave all existing
+                                // MX records alone
+            'records' => array(
+                array('pointer' => 'mail', 'pref' => 10 ),
+            ),
+        ),
+
+
+        // Example 'A' record
+        'a' => array(
+            'replace' => 'match', // Set to 'match' to remove all records
+                                 // which share the same hostname
+                                 // (ie. 'www')
+
+            'records' => array(
+                // Notice the trailing '.' on the A record for the
+                // domain itself.  For all other records, use the short
+                // hostname and do not append a '.'
+                array('hostname' =>
+                          $_SESSION['beatnik']['curdomain']['zonename'] . '.',
+                      'ipaddr'    => '10.0.0.3',
+                ),
+
+                array('hostname' => 'www', 'ipaddr'   => '10.0.0.3' ),
+            ),
+        ),
+    ),
+);
+
diff --git a/beatnik/config/conf.xml b/beatnik/config/conf.xml
new file mode 100644 (file)
index 0000000..a319fa5
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<!-- $Horde: beatnik/config/conf.xml,v 1.5 2008/05/15 04:20:53 bklang Exp $ -->
+<configuration>
+ <configsection name="storage">
+  <configheader>Beatnik Storage</configheader>
+  <configswitch name="driver" desc="What backend should we use for storing DNS
+  records?">
+   <case name="pdnsgsql" desc="PowerDNS Generic SQL">
+    <configsection name="params">
+     <configsql switchname="driverconfig">
+      <configstring name="domains_table" desc="Table that holds the list of domains. [domains]" required="false" />
+      <configstring name="records_table" desc="Table that holds the list of domains. [records]" required="false" />
+     </configsql>
+    </configsection>
+   </case>
+   <case name="ldap2dns" desc="ldap2dns (LDAP)">
+    <configsection name="params">
+     <configstring name="hostspec" desc="The hostname of the LDAP
+     server">localhost</configstring>
+     <configstring name="basedn" desc="The base DN for the LDAP server"/>
+     <configstring name="binddn" required="false" desc="The DN used to bind to
+     the LDAP server"/>
+     <configstring name="password" required="false" desc="The password used to
+     bind to the LDAP server"/>
+     <configenum name="version" desc="LDAP Protocol Version">3 
+      <values>3 
+       <value desc="LDAPv2 (Deprecated)">2</value>
+       <value desc="LDAPv3">3</value>
+      </values>
+     </configenum>
+     <configstring name="dn" desc="The object search key"/>
+     <configswitch name="filter_type" desc="How to specify a filter for the
+     zone lists">objectclass 
+      <case name="objectclass" desc="One or more objectclass filters">
+       <configlist name="objectclass" desc="The objectclass filter used to
+       search for zones. Can be a single objectclass or a list."/>dnszone
+      </case>
+      <case name="free" desc="A complete LDAP filter expression">
+       <configstring name="filter" desc="The LDAP RFC formatted filter used to
+       search for users."/>
+      </case>
+     </configswitch>
+    </configsection>
+   </case>
+   <case name="sql" desc="SQL">
+    <configsection name="params">
+     <configsql switchname="driverconfig">
+     </configsql>
+    </configsection>
+   </case>
+  </configswitch>
+ </configsection>
+ <configsection name="menu">
+   <configheader>Menu Settings</configheader>
+   <configmultienum name="apps" desc="Select any applications that should be
+                    linked in Beatnik's menu">
+     <values>
+       <configspecial name="list-horde-apps" />
+     </values>
+   </configmultienum>
+ </configsection>
+</configuration>
diff --git a/beatnik/config/prefs.php.dist b/beatnik/config/prefs.php.dist
new file mode 100644 (file)
index 0000000..f12c496
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * $Horde: beatnik/config/prefs.php.dist,v 1.5 2008/03/07 17:53:34 bklang Exp $
+ *
+ * See horde/config/prefs.php for documentation on the structure of this file.
+ */
+
+$prefGroups['display'] = array(
+    'column' => _("Options"),
+    'label' => _("Display Preferences"),
+    'desc' => _("Set default display parameters."),
+    'members' => array('domain_groups', 'domains_perpage')
+);
+
+$prefGroups['defaults'] = array(
+    'column' => _("Options"),
+    'label' => _("Record Defaults"),
+    'desc' => _("Set default record parameters."),
+    'members' => array('default_ttl')
+);
+
+// user domain groups
+$_prefs['domain_groups'] = array(
+    'value' => '',
+    'locked' => false,
+    'shared' => false,
+    'type' => 'implicit'
+);
+
+// listing
+$_prefs['domains_perpage'] = array(
+    'value' => 20,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'number',
+    'desc' => _("How many domain to display per page.")
+);
+
+$_prefs['default_ttl'] = array(
+    'value' => '86400',
+    'locked' => false,
+    'shared' => false,
+    'type' => 'number',
+    'desc' => _("Default Time-To-Live for new records.")
+);
diff --git a/beatnik/delete.php b/beatnik/delete.php
new file mode 100644 (file)
index 0000000..8146a65
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Delete records
+ *
+ * Copyright 2006-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (BSD). If you
+ * did not receive this file, see http://cvs.horde.org/co.php/merk/LICENSE.
+ *
+ * $Horde: beatnik/delete.php,v 1.15 2009/07/03 10:05:29 duck Exp $
+ */
+
+define('BEATNIK_BASE', dirname(__FILE__));
+require_once BEATNIK_BASE . '/lib/base.php';
+require_once BEATNIK_BASE . '/lib/Forms/DeleteRecord.php';
+
+$vars = Horde_Variables::getDefaultVariables();
+list($type, $record) = $beatnik_driver->getRecord(Horde_Util::getFormData('id'));
+
+$form = new DeleteRecord($vars);
+
+if ($form->validate($vars)) {
+    $form->getInfo($vars, $info);
+    if (Horde_Util::getFormData('submitbutton') == _("Delete")) {
+        $result = $beatnik_driver->deleteRecord($info);
+        if (is_a($result, 'PEAR_Error')) {
+            $notification->push($result->getMessage() . ': ' . $result->getDebugInfo(), 'horde.error');
+            header('Location: ' . Horde_Util::addParameter(Horde::applicationUrl('viewzone.php'), $info));
+        } else {
+            $notification->push(_("Record deleted"), 'horde.success');
+            if ($info['rectype'] == 'soa') {
+                header('Location: ' . Horde::applicationUrl('listzones.php'));
+            } else {
+                header('Location: ' . Horde::applicationUrl('viewzone.php'));
+            }
+        }
+    } else {
+        $notification->push(_("Record not deleted"), 'horde.warning');
+        header('Location: ' . Horde_Util::addParameter(Horde::applicationUrl('viewzone.php'), $info));
+    }
+    exit;
+} elseif (!$form->isSubmitted() && $record) {
+    foreach ($record as $field => $value) {
+        $vars->set($field, $value);
+    }
+}
+
+
+$title = _("Delete");
+require BEATNIK_BASE . '/templates/common-header.inc';
+require BEATNIK_BASE . '/templates/menu.inc';
+
+$form->renderActive(null, null, Horde::applicationUrl('delete.php'), 'post');
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/beatnik/docs/CHANGES b/beatnik/docs/CHANGES
new file mode 100644 (file)
index 0000000..dd5f72e
--- /dev/null
@@ -0,0 +1,5 @@
+---
+0.1
+---
+
+[beatnik] Initial Release
diff --git a/beatnik/docs/CREDITS b/beatnik/docs/CREDITS
new file mode 100644 (file)
index 0000000..6e32ed4
--- /dev/null
@@ -0,0 +1,32 @@
+===========================
+ Beatnik Development Team
+===========================
+
+:Last update:   $Date: 2007/04/22 04:50:38 $
+:Revision:      $Revision: 1.6 $
+
+
+Core Developers
+===============
+Ben Klang <bklang@alkaloid.net>
+Duck <duck@obala.net>
+
+
+Drivers
+=======
+ldap2dns - Ben Klang <bklang@alkaloid.net>
+sql      - Duck <duck@obala.net>
+
+
+Localization
+============
+Slovenian               Duck <duck@obala.net>
+
+=====================   ======================================================
+=====================   ======================================================
+
+
+Contributions
+=============
+
+$Horde: beatnik/docs/CREDITS,v 1.6 2007/04/22 04:50:38 chuck Exp $
diff --git a/beatnik/docs/INSTALL b/beatnik/docs/INSTALL
new file mode 100644 (file)
index 0000000..2a835b8
--- /dev/null
@@ -0,0 +1,215 @@
+=========================
+ Installing Beatnik 1.0
+=========================
+
+:Last update:   $Date: 2007/06/19 09:56:38 $
+:Revision:      $Revision: 1.4 $
+
+.. contents:: Contents
+.. section-numbering::
+
+This document contains instructions for installing the Beatnik ...
+
+For information on the capabilities and features of Beatnik, see the file
+README_ in the top-level directory of the Beatnik distribution.
+
+
+Obtaining Beatnik
+==================
+
+Beatnik can be obtained from the Horde website and FTP server, at
+
+   http://www.horde.org/beatnik/
+
+   ftp://ftp.horde.org/pub/beatnik/
+
+Or use the mirror closest to you:
+
+   http://www.horde.org/mirrors.php
+
+Bleeding-edge development versions of Beatnik are available via CVS; see the
+file `horde/docs/HACKING`_ in the Horde distribution, or the website
+http://www.horde.org/source/, for information on accessing the Horde CVS
+repository.
+
+
+Prerequisites
+=============
+
+To function properly, Beatnik **requires** the following:
+
+1. A working Horde installation.
+
+   Beatnik runs within the `Horde Application Framework`_, a set of common
+   tools for Web applications written in PHP.  You must install Horde before
+   installing Beatnik.
+
+   .. Important:: Beatnik 1.0 requires version 3.0+ of the Horde Framework -
+                  earlier versions of Horde will **not** work.
+
+   .. _`Horde Application Framework`: http://www.horde.org/horde/
+
+   The Horde Framework can be obtained from the Horde website and FTP server,
+   at
+
+      http://www.horde.org/horde/
+
+      ftp://ftp.horde.org/pub/horde/
+
+   Many of Beatnik's prerequisites are also Horde prerequisites.
+
+   .. Important:: Be sure to have completed all of the steps in the
+                  `horde/docs/INSTALL`_ file for the Horde Framework before
+                  installing Beatnik.
+
+2. The following PHP capabilities:
+
+   a. FOO support ``--with-foo`` [OPTIONAL]
+
+      Description of Foo and what it is used for.
+
+3. The following PEAR modules:
+   (See `horde/docs/INSTALL`_ for instructions on installing PEAR modules)
+
+   a. PEAR_Package x.x.x [OPTIONAL]
+  
+      Beatnik uses the Foo_Bar class for...
+
+4. Something else.
+
+The following items are not required, but are strongly **recommended**:
+
+1. Yet something else.
+
+
+Installing Beatnik
+===================
+
+Beatnik is written in PHP, and must be installed in a web-accessible
+directory.  The precise location of this directory will differ from system to
+system.  Conventionally, Beatnik is installed directly underneath Horde in
+the web server's document tree.
+
+Since Beatnik is written in PHP, there is no compilation necessary; simply
+expand the distribution where you want it to reside and rename the root
+directory of the distribution to whatever you wish to appear in the URL.  For
+example, with the Apache web server's default document root of
+``/usr/local/apache/htdocs``, you would type::
+
+   cd /usr/local/apache/htdocs/horde
+   tar zxvf /path/to/beatnik-x.y.z.tar.gz
+   mv beatnik-x.y.z beatnik
+
+and would then find Beatnik at the URL::
+
+   http://your-server/horde/beatnik/
+
+
+Configuring Beatnik
+====================
+
+1. Configuring Horde for Beatnik
+
+   a. Register the application
+
+      In ``horde/config/registry.php``, find the ``applications['beatnik']``
+      stanza.  The default settings here should be okay, but you can change
+      them if desired.  If you have changed the location of Beatnik relative
+      to Horde, either in the URL, in the filesystem or both, you must update
+      the ``fileroot`` and ``webroot`` settings to their correct values.
+
+2. Configuring Beatnik
+
+   To configure Beatnik, change to the ``config/`` directory of the installed
+   distribution, and make copies of all of the configuration ``dist`` files
+   without the ``dist`` suffix::
+
+      cd config/
+      for foo in *.dist; do cp $foo `basename $foo .dist`; done
+
+   Or on Windows::
+
+      copy *.dist *.
+
+   Documentation on the format and purpose of those files can be found in each
+   file.  You may edit these files if you wish to customize Beatnik's
+   appearance and behavior.  With one exception (``foo.php``) the defaults
+   will be correct for most sites.
+
+   You must login to Horde as a Horde Administrator to finish the
+   configuration of Beatnik.  Use the Horde ``Administration`` menu item to
+   get to the administration page, and then click on the ``Configuration``
+   icon to get the configuration page.  Select ``Beatnik Name`` from the
+   selection list of applications.  Fill in or change any configuration values
+   as needed.  When done click on ``Generate Beatnik Name Configuration`` to
+   generate the ``conf.php`` file.  If your web server doesn't have write
+   permissions to the Beatnik configuration directory or file, it will not be
+   able to write the file.  In this case, go back to ``Configuration`` and
+   choose one of the other methods to create the configuration file
+   ``beatnik/config/conf.php``.
+
+   Note for international users: Beatnik uses GNU gettext to provide local
+   translations of text displayed by applications; the translations are found
+   in the ``po/`` directory.  If a translation is not yet available for your
+   locale (and you wish to create one), see the ``horde/po/README`` file, or
+   if you're having trouble using a provided translation, please see the
+   `horde/docs/TRANSLATIONS`_ file for instructions.
+
+3. More instructions, upgrading, securing, etc.
+
+4. Testing Beatnik
+
+   Once you have configured Beatnik, bring up the included test page in your
+   Web browser to ensure that all necessary prerequisites have been met.  See
+   the `horde/docs/INSTALL`_ document for further details on Horde test
+   scripts.  If you installed Beatnik as described above, the URL to the test
+   page would be::
+
+      http://your-server/horde/beatnik/test.php
+
+   The test script will also allow you to test...
+   Next, use Beatnik to....  Test at least the following:
+
+   - Foo
+   - Bar
+
+
+Known Problems
+==============
+
+...
+
+
+Obtaining Support
+=================
+
+If you encounter problems with Beatnik, help is available!
+
+The Horde Frequently Asked Questions List (FAQ), available on the Web at
+
+  http://www.horde.org/faq/
+
+The Horde Project runs a number of mailing lists, for individual applications
+and for issues relating to the project as a whole.  Information, archives, and
+subscription information can be found at
+
+  http://www.horde.org/mail/
+
+Lastly, Horde developers, contributors and users may also be found on IRC,
+on the channel #horde on the Freenode Network (irc.freenode.net).
+
+Please keep in mind that Beatnik is free software written by volunteers.
+For information on reasonable support expectations, please read
+
+  http://www.horde.org/support.php
+
+Thanks for using Beatnik!
+
+The Beatnik team
+
+
+.. _README: ?f=README.html
+.. _`horde/docs/HACKING`: ../../horde/docs/?f=HACKING.html
+.. _`horde/docs/INSTALL`: ../../horde/docs/?f=INSTALL.html
+.. _`horde/docs/TRANSLATIONS`: ../../horde/docs/?f=TRANSLATIONS.html
diff --git a/beatnik/docs/RELEASE_NOTES b/beatnik/docs/RELEASE_NOTES
new file mode 100644 (file)
index 0000000..0702c75
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Release focus. Possible values:
+ * 0 - N/A
+ * 1 - Initial freshmeat announcement
+ * 2 - Documentation
+ * 3 - Code cleanup
+ * 4 - Minor feature enhancements
+ * 5 - Major feature enhancements
+ * 6 - Minor bugfixes
+ * 7 - Major bugfixes
+ * 8 - Minor security fixes
+ * 9 - Major security fixes
+ */
+$this->notes['fm']['focus'] = 4;
+
+/* Mailing list release notes. */
+$this->notes['ml']['changes'] = <<<ML
+The Horde Team is pleased to announce the [first release candidate|final
+release] of the Skeleton Foo Bar Application version H3 (x.x).
+
+The Skeleton Foo Bar Application is a...
+
+[For alpha/beta releases:
+This is a preview version that should not be used on production systems.  This
+version is considered feature complete but there might still be a few bugs.
+You should not use this preview version over existing production data.
+
+We encourage widespread testing and feedback via the mailing lists or our bug
+tracking system.  Updated translations are very welcome, though some strings
+might still change before the final release.]
+
+[For release candidates:
+Barring any problems, this code will be released as Skeleton H3 (x.x).
+Testing is requested and comments are encouraged.
+Updated translations would also be great.]
+
+The major changes compared to the Skeleton version H3 (x.x) are:
+[or: Changes in this release:]
+    * ...
+ML;
+
+/* Freshmeat release notes. */
+$this->notes['fm']['changes'] = <<<FM
+FM;
+
+$this->notes['name'] = 'Skeleton';
+$this->notes['fm']['project'] = 'skeleton';
+$this->notes['fm']['branch'] = 'Default';
diff --git a/beatnik/docs/TODO b/beatnik/docs/TODO
new file mode 100644 (file)
index 0000000..c74dc4a
--- /dev/null
@@ -0,0 +1,17 @@
+================================
+ Beatnik Development TODO List
+================================
+$Horde: beatnik/docs/TODO,v 1.5 2007/04/12 01:46:42 bklang Exp $
+
+:Last update:   $Date: 2007/04/12 01:46:42 $
+:Revision:      $Revision: 1.5 $
+:Contact:       
+
+
+* Create an api which can be used by XML-RPC where remote clients can
+  authenticate to horde and update their own DNS entries (subject to built-in
+  permissions scheme).
+
+* Allow sorting on arbitrary fields
+
+* Fix deleting entire domains
diff --git a/beatnik/editrec.php b/beatnik/editrec.php
new file mode 100644 (file)
index 0000000..0a0488b
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * $Horde: beatnik/editrec.php,v 1.35 2009/07/03 10:05:29 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+define('BEATNIK_BASE', dirname(__FILE__));
+require_once BEATNIK_BASE . '/lib/base.php';
+require_once BEATNIK_BASE . '/lib/Forms/EditRecord.php';
+
+$vars = Horde_Variables::getDefaultVariables();
+$url = Horde::applicationUrl('editrec.php');
+list($type, $record) = $beatnik_driver->getRecord(Horde_Util::getFormData('id'));
+
+$form = new EditRecord($vars);
+
+if ($form->validate($vars)) {
+    $form->getInfo($vars, $info);
+    $result = $beatnik_driver->saveRecord($info);
+
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push($result->getMessage() . ': ' . $result->getDebugInfo(), 'horde.error');
+    } else {
+        $notification->push('Record data saved.', 'horde.success');
+
+        // Check to see if this is a new domain
+        $edit = $vars->get('id');
+        if ($info['rectype'] == 'soa' && !$edit) {
+            // if added a soa redirect to the autogeneration page
+            $url = Horde_Util::addParameter(Horde::applicationUrl('autogenerate.php'),
+                                      array('rectype' => 'soa', 'curdomain' => $info['zonename']), false, false);
+        } else {
+            $url = Horde::applicationUrl('viewzone.php');
+        }
+    }
+
+    header('Location: ' . $url);
+    exit;
+
+} elseif (!$form->isSubmitted() && $record) {
+    foreach ($record as $field => $value) {
+        $vars->set($field, $value);
+    }
+}
+
+$title = $form->getTitle();
+require BEATNIK_TEMPLATES . '/common-header.inc';
+require BEATNIK_TEMPLATES . '/menu.inc';
+
+$form->renderActive(null, null, $url, 'post');
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/beatnik/index.php b/beatnik/index.php
new file mode 100644 (file)
index 0000000..7464ecf
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+/**
+ * $Horde: beatnik/index.php,v 1.5 2008/08/22 08:53:50 duck Exp $
+ *
+ * Copyright 2005 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+define('BEATNIK_BASE', dirname(__FILE__));
+$beatnik_configured = (is_readable(BEATNIK_BASE . '/config/conf.php') &&
+                       is_readable(BEATNIK_BASE . '/config/prefs.php'));
+
+if (!$beatnik_configured) {
+    require BEATNIK_BASE . '/../lib/Test.php';
+    Horde_Test::configFilesMissing('Beatnik', BEATNIK_BASE,
+        array('conf.php', 'prefs.php'));
+}
+
+require BEATNIK_BASE . '/listzones.php';
diff --git a/beatnik/js/beatnik.js b/beatnik/js/beatnik.js
new file mode 100644 (file)
index 0000000..051114b
--- /dev/null
@@ -0,0 +1,3 @@
+
+var loading;function domainSubmit(clear)
+{if(document.menu.domainSelector[document.menu.domainSelector.selectedIndex].value!=''){if((loading==null)||(clear!=null)){loading=true;document.menu.submit();}}}
\ No newline at end of file
diff --git a/beatnik/js/src/beatnik.js b/beatnik/js/src/beatnik.js
new file mode 100644 (file)
index 0000000..cb7221a
--- /dev/null
@@ -0,0 +1,14 @@
+// $Horde: beatnik/js/src/beatnik.js,v 1.3 2008/08/20 08:56:53 duck Exp $
+
+// Menu Domain submit
+var loading;
+function domainSubmit(clear)
+{
+    if (document.menu.domainSelector[document.menu.domainSelector.selectedIndex].value != '') {
+         if ((loading == null) || (clear != null)) {
+            loading = true;
+            document.menu.submit();
+         }
+    }
+}
+
diff --git a/beatnik/lib/Application.php b/beatnik/lib/Application.php
new file mode 100644 (file)
index 0000000..0cffe8e
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Beatnik application API.
+ *
+ * @package Beatnik
+ */
+class Beatnik_Application extends Horde_Registry_Application
+{
+    public $version = 'H4 (1.0-git)';
+
+    /**
+     * Returns a list of available permissions.
+     *
+     * @return array  An array describing all available permissions.
+     */
+    public function perms()
+    {
+    static $perms = array();
+
+    if (!empty($perms)) {
+        return $perms;
+    }
+
+    require_once dirname(__FILE__) . '/base.php';
+
+    $perms['title']['beatnik:domains'] = _("Domains");
+
+    // Run through every domain
+    foreach ($GLOBALS['beatnik_driver']->getDomains() as $domain) {
+        $perms['tree']['beatnik']['domains'][$domain['zonename']] = false;
+        $perms['title']['beatnik:domains:' . $domain['zonename']] = $domain['zonename'];
+    }
+
+    return $perms;
+    }
+
+    /**
+     * Generate the menu to use on the prefs page.
+     *
+     * @return Horde_Menu  A Horde_Menu object.
+     */
+    public function prefsMenu()
+    {
+        return Beatnik::getMenu();
+    }
+}
diff --git a/beatnik/lib/Beatnik.php b/beatnik/lib/Beatnik.php
new file mode 100644 (file)
index 0000000..b60325a
--- /dev/null
@@ -0,0 +1,590 @@
+<?php
+/**
+ * Beatnik base class
+ *
+ * $Horde: beatnik/lib/Beatnik.php,v 1.53 2009/07/15 15:05:32 duck Exp $
+ *
+ * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Beatnik
+ */
+class Beatnik {
+
+    /**
+     * Build Beatnik's list of menu items.
+     */
+    function getMenu($returnType = 'object')
+    {
+        // We are editing rather than adding if an ID was passed
+        $editing = Horde_Util::getFormData('id');
+        $editing = !empty($editing);
+
+        $hordeImgDir = $GLOBALS['registry']->getImageDir('horde');
+        $beatnikImgDir = $GLOBALS['registry']->getImageDir();
+        $menu = new Horde_Menu();
+
+        $menu->add(Horde::applicationUrl('listzones.php'), _('List Domains'), 'website.png', $hordeImgDir);
+        if (!empty($_SESSION['beatnik']['curdomain'])) {
+            $menu->add(Horde_Util::addParameter(Horde::applicationUrl('editrec.php'), 'curdomain', $_SESSION['beatnik']['curdomain']['zonename']), ($editing) ? _("Edit Record") : _("Add Record"), 'edit.png', $hordeImgDir);
+        } else {
+            $menu->add(Horde::applicationUrl('editrec.php?rectype=soa'), _("Add Zone"), 'edit.png', $hordeImgDir);
+        }
+
+        $url = Horde_Util::addParameter(Horde::selfUrl(true), array('expertmode' => 'toggle'));
+        $menu->add($url, _('Expert Mode'), 'hide_panel.png', $hordeImgDir, '', null, ($_SESSION['beatnik']['expertmode']) ? 'current' : '');
+
+        if (count(Beatnik::needCommit())) {
+            $url = Horde_Util::addParameter(Horde::applicationUrl('commit.php'), array('domain' => 'all'));
+            $menu->add($url, _('Commit All'), 'commit-all.png', $beatnikImgDir);
+        }
+
+        if ($returnType == 'object') {
+           return $menu;
+        } else {
+            return $menu->render();
+        }
+    }
+
+    /**
+     * Get possible records
+     *
+     * The keys of this array are the IDs of the record type.  The values
+     * are a human friendly description of the record type.
+     */
+    function getRecTypes()
+    {
+        $records = array(
+            'soa' => _("SOA (Start of Authority)"),
+            'ns' => _("NS (Name Server)"),
+            'a' => _("A (Address)"),
+            'ptr' => _("PTR (Reverse DNS)"),
+            'cname' => _("CNAME (Alias)"),
+            'mx' => _("MX (Mail eXchange)"),
+            'srv' => _("SRV (Service Record)"),
+            'txt' => _("TXT (Text Record)"),
+        );
+
+        return array_merge($records, $GLOBALS['beatnik_driver']->getRecDriverTypes());
+    }
+
+    /**
+     */
+    function getRecFields($recordtype)
+    {
+        // Record Format:
+        // $recset is an array of fields.  The field IDs are the keys.
+        // Each field is an array with the following keys:
+        // 'name': The short name of the field.  This key is also used
+        //      to reference the help system.
+        // 'description': Long description of the field
+        // 'type': Field type.  Choose from any available from Horde_Form
+        // 'maxlength': Maximum field length.  0 is unlimited
+        // 'required': If true, the field will be required by the form
+        // 'infoset': one of 'basic' or 'advanced'.  This is used to help keep
+        //      the forms simple for non-power-users.  If 'required' is true and
+        //      'infoset' is false then 'default' MUST be specified
+        // 'default': the default value of the field.
+        // 'index': Crude sort ordering.  Lower means show higher in the group
+
+        // Attempt to return cached results.
+        static $recset = array();
+
+        if (isset($recset[$recordtype])) {
+            return $recset[$recordtype];
+        }
+        $recset[$recordtype] = array();
+
+        $recset[$recordtype]['id'] = array(
+            'name' => _("UID"),
+            'description' => _("Unique Identifier (Used as Record ID)"),
+            'type' => 'hidden',
+            'maxlength' => 0,
+            'required' => false, // Empty for "new" entries
+            'infoset' => 'basic',
+            'index' => 0,
+        );
+
+        switch (strtolower($recordtype)) {
+        case 'soa':
+            $recset[$recordtype]['zonename'] = array(
+                'name' => _("Domain Name"),
+                'description' => _("Zone Domain Name"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['zonens'] = array(
+                'name' => _("Primary Nameserver"),
+                'description' => _("Primary nameserver for this zone"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            $recset[$recordtype]['zonecontact'] = array(
+                'name' => _("Zone Contact"),
+                'description' => _("Contact e-mail address for this zone"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            $recset[$recordtype]['serial'] = array(
+                'name' => _("Serial"),
+                'description' => _("Zone Serial Number"),
+                'type' => 'int',
+                'default' => date('Ymd'). '00',
+                'maxlength' => 0,
+                'required' => false,
+                'infoset' => 'advanced',
+                'index' => 3,
+            );
+            $recset[$recordtype]['refresh'] = array(
+                'name' => 'Refresh',
+                'description' => _("Zone Refresh"),
+                'type' => 'int',
+                'maxlength' => 0,
+                'required' => false,
+                'infoset' => 'advanced',
+                'index' => 4,
+            );
+            $recset[$recordtype]['retry'] = array(
+                'name' => _("Retry"),
+                'description' => _("Zone Retry"),
+                'type' => 'int',
+                'maxlength' => 0,
+                'required' => false,
+                'infoset' => 'advanced',
+                'index' => 5,
+            );
+            $recset[$recordtype]['expire'] = array(
+                'name' => _("Expiration"),
+                'description' => _("Zone Expiry"),
+                'type' => 'int',
+                'maxlength' => 0,
+                'required' => false,
+                'infoset' => 'advanced',
+                'index' => 6,
+            );
+            $recset[$recordtype]['minimum'] = array(
+                'name' => _("Minimum"),
+                'description' => _("Zone Minimum"),
+                'type' => 'int',
+                'maxlength' => 0,
+                'required' => false,
+                'infoset' => 'advanced',
+                'index' => 7,
+            );
+            break;
+
+        case 'a':
+            $recset[$recordtype]['hostname'] = array(
+                'name' => _("Hostname"),
+                'description' => _("Short hostname for this record"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['ipaddr'] = array(
+                'name' => _("IP Address"),
+                'description' => _("IPv4 Network Address"),
+                'type' => 'ipaddress',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            break;
+
+        case 'ptr':
+            $recset[$recordtype]['hostname'] = array(
+                'name' => _("Hostname"),
+                'description' => _("IP in Reverse notation (.in-addr.arpa)"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['pointer'] = array(
+                'name' => _("Hostname Target"),
+                'description' => _("Hostname for Reverse DNS"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            break;
+
+        case 'mx':
+            $recset[$recordtype]['pointer'] = array(
+                'name' => _("Hostname Target"),
+                'description' => _("Hostname of Mail eXchanger"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['pref'] = array(
+                'name' => _("Preference"),
+                'description' => _("MX Preference (lower is more preferred)"),
+                'type' => 'int',
+                'default' => 0,
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            break;
+
+        case 'cname':
+            $recset[$recordtype]['hostname'] = array(
+                'name' => _("Hostname"),
+                'description' => _("Short hostname for this record"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['pointer'] = array(
+                'name' => _("Hostname Target"),
+                'description' => _("Hostname for CNAME alias"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            break;
+
+        case 'ns':
+            $recset[$recordtype]['hostname'] = array(
+                'name' => _("Domain Name"),
+                'description' => _("Short sub-domain for NS record (leave blank unless creating a subdomain)"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => false,
+                // If we have a current domain name, use it for the default val
+                'default' => @$GLOBALS['curdomain']['zonename'],
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['pointer'] = array(
+                'name' => _("Hostname Target"),
+                'description' => _("Hostname of Authoritative Name Server"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            break;
+
+        case 'srv':
+            $recset[$recordtype]['hostname'] = array(
+                'name' => _("Hostname"),
+                'description' => _("Short hostname for this record"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['pointer'] = array(
+                'name' => _("Hostname Target"),
+                'description' => _("Hostname for DNS Service Record"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            $recset[$recordtype]['priority'] = array(
+                'name' => _("SRV Priority"),
+                'description' => _("DNS Service Record Priority"),
+                'type' => 'int',
+                'default' => 0,
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 3,
+            );
+            $recset[$recordtype]['weight'] = array(
+                'name' => _("SRV Weight"),
+                'description' => _("DNS Service Record Weight"),
+                'type' => 'int',
+                'default' => 0,
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 4,
+            );
+            $recset[$recordtype]['port'] = array(
+                'name' => _("SRV Port"),
+                'description' => _("DNS Service Record Port Number"),
+                'type' => 'int',
+                'default' => 0,
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 5,
+            );
+            break;
+
+        case 'txt':
+            $recset[$recordtype]['hostname'] = array(
+                'name' => _("Hostname"),
+                'description' => _("Short hostname for this record"),
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset[$recordtype]['text'] = array(
+                'name' => 'Text',
+                'description' => _("String payload for DNS TXT"),
+                'type' => 'text',
+                'maxlength' => 256,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            break;
+        }
+
+        $recset[$recordtype]['ttl'] = array(
+            'name' => _("TTL"),
+            'description' => _("Record Time-To-Live (seconds)"),
+            'type' => 'int',
+            'maxlength' => 0,
+            'required' => false,
+            'infoset' => 'advanced',
+            'index' => 100,
+            'default' => $GLOBALS['prefs']->getValue('default_ttl')
+        );
+
+        $recset[$recordtype] = array_merge($recset[$recordtype], $GLOBALS['beatnik_driver']->getRecDriverFields($recordtype));
+        uasort($recset[$recordtype], array('Beatnik', 'fieldSort'));
+
+        return $recset[$recordtype];
+    }
+
+    /**
+     * Check or set a flag to show that a domain has outstanding changes that
+     * need to be committed.
+     *
+     * @param optional string  $domain      Domain to check whether a commit is
+     *                                      necessary
+     * @param optional boolean $needcommit  true adds the domain to the list
+     *                                      that needs committing; false removes
+     *                                      the domain from the list
+     *
+     * @return mixed  Array of domains needing committing if no arguments are
+     *                passed.
+     *                Boolean if only a $domain is passed: True if $domain has
+     *                outstanding changes, false if not.
+     *                Mixed if both $domain and $needcommit are passed.  True
+     *                on success, PEAR::Error on error.
+     */
+    function needCommit($domain = null, $needcommit = null)
+    {
+        // Make sure we have a valid array with which to work
+        if (!isset($_SESSION['beatnik']['needcommit'])) {
+            $_SESSION['beatnik']['needcommit'] = array();
+        }
+
+        if ($domain === null && $needcommit === null) {
+            // Return the stored list of domains needing changes committed.
+            return array_keys($_SESSION['beatnik']['needcommit']);
+        } elseif ($domain !== null && $needcommit === null) {
+            // Check if domain need committing
+            return isset($_SESSION['beatnik']['needcommit'][$domain]);
+        } elseif ($domain !== null && is_bool($needcommit)) {
+            // Flag domain for committing
+            if ($needcommit) {
+                if(!isset($_SESSION['beatnik']['needcommit'][$domain])) {
+                    $_SESSION['beatnik']['needcommit'][$domain] = true;
+                }
+            } else {
+                if (isset($_SESSION['beatnik']['needcommit'][$domain])) {
+                    unset($_SESSION['beatnik']['needcommit'][$domain]);
+                }
+            }
+            return true;
+        } else {
+            // Somebody sent something they should not have...
+            return PEAR::raiseError(_("Unable to determine if domain needs committing: invalid parameter."));
+        }
+    }
+
+    /**
+     * Checks for the given permissions for the current user on the given
+     * permissions node.  Optionally check for the requested permssion for a
+     * given number of steps up the tree.
+     *
+     * @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 True if the user has permission, False if not
+     */
+    function hasPermission($permname, $permmask = null, $numparents = 0)
+    {
+        if (Horde_Auth::isAdmin()) {
+            return true;
+        }
+
+        $perms = Perms::singleton();
+        if ($permmask === null) {
+            $permmask = PERMS_SHOW|PERMS_READ;
+        }
+
+        # Default deny all permissions
+        $user = 0;
+        $superadmin = 0;
+
+        $superadmin = $perms->hasPermission('beatnik:domains', 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--;
+        }
+        return (($superadmin | $user) & $permmask);
+    }
+
+    /**
+     * Autogenerate a set of records from a template defined in
+     * config/autogenerate.php
+     *
+     * @param object $vars  Horde_Variables object from Autogenerate form
+     *
+     * @return mixed  true on success, PEAR::Error on failure
+     */
+    function autogenerate(&$vars)
+    {
+
+        require BEATNIK_BASE . '/config/autogenerate.php';
+        $template = $templates[$vars->get('template')];
+        $zonedata = $GLOBALS['beatnik_driver']->getRecords($_SESSION['beatnik']['curdomain']['zonename']);
+        if (is_a($zonedata, 'PEAR_Error')) {
+            return $zonedata;
+        }
+
+        foreach ($template['types'] as $rectype => $definitions) {
+            // Only attempt to delete records if the type is already defined
+            if (isset($zonedata[$rectype])) {
+                // Check for collisions and handle as requested
+                switch($definitions['replace']) {
+                case 'all':
+                    foreach ($zonedata[$rectype] as $record) {
+                        $result = $GLOBALS['beatnik_driver']->deleteRecord($record);
+                        if (is_a($result, 'PEAR_Error')) {
+                            $GLOBALS['notification']->push($result);
+                        }
+                    }
+                    break;
+
+                case 'match':
+                    foreach ($zonedata[$rectype] as $record) {
+                        // Check every record in the template to see if the
+                        // hostname matches
+                        foreach ($definitions['records'] as $Trecord) {
+                            if ($record['hostname'] == $Trecord['hostname']) {
+                                $result = $GLOBALS['beatnik_driver']->deleteRecord($record);
+                                if (is_a($result, 'PEAR_Error')) {
+                                    $GLOBALS['notification']->push($result);
+                                }
+                            }
+                        }
+                    }
+                    break;
+
+                #case 'none':
+                #default:
+                }
+            }
+
+            $defaults = array('rectype' => $rectype,
+                              'zonename'=> $_SESSION['beatnik']['curdomain']['zonename']);
+            foreach ($definitions['records'] as $info) {
+                if ($GLOBALS['beatnik_driver']->recordExists($info, $rectype)) {
+                    $GLOBALS['notification']->push(_("Skipping existing identical record"));
+                    continue;
+                }
+                $result = $GLOBALS['beatnik_driver']->saveRecord(array_merge($defaults, $info));
+                if (is_a($result, 'PEAR_Error')) {
+                    $GLOBALS['notification']->push($result->getMessage() . ': ' . $result->getDebugInfo(), 'horde.error');
+                } else {
+                    $GLOBALS['notification']->push(sprintf(_('Record added: %s/%s'), $rectype, $info['hostname']), 'horde.success');
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Increments a domain serial number.
+     *
+     * @param int $serial  Serial number to be incremented
+     *
+     * @return int  Incremented serial number
+     */
+    function incrementSerial($serial)
+    {
+        // Create a serial number of the ad-hoc standard YYYYMMDDNN
+        // where YYYYMMDD is the year/month/day of the last update to this
+        // odmain and NN is an incrementer to handle multiple updates in a
+        // given day.
+        $newserial = (int) (date('Ymd') . '00');
+        if ($serial < $newserial) {
+            return $newserial;
+        } else {
+            return ++$serial;
+        }
+    }
+
+    /**
+     * Callback for usort to make field data print in a friendly order
+     *
+     * @param mixed $a First sort variable
+     * @param mixed $b Second sort variable
+     *
+     * @return int -1, 0, 1 based on relative sort order
+     */
+    function fieldSort($a, $b)
+    {
+        if ($a['index'] < $b['index']) {
+            return -1;
+        } elseif ($a['index'] > $b['index']) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+}
diff --git a/beatnik/lib/Driver.php b/beatnik/lib/Driver.php
new file mode 100644 (file)
index 0000000..62e4e09
--- /dev/null
@@ -0,0 +1,330 @@
+<?php
+/**
+ * Beatnik_Driver:: defines an API implementing astorage backends for Beatnik.
+ *
+ * $Horde: beatnik/lib/Driver.php,v 1.21 2009/07/15 15:05:32 duck Exp $
+ *
+ * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Ben Klang <ben@alkaloid.net>
+ * @version $Revision: 1.21 $
+ * @package Beatnik
+ */
+class Beatnik_Driver {
+
+    /**
+     * Hash containing connection parameters.
+     *
+     * @var array $_params
+     */
+    var $_params = array();
+
+    function Beatnik_Driver($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /**
+     * Get any record types  available specifically in this driver.
+     *
+     * @return array Records available only to this driver
+     */
+    function getRecDriverTypes()
+    {
+        return array();
+    }
+
+
+    /**
+     * Get any fields available specifically in this driver by record type.
+     *
+     * @param string $type Record type for which fields should be returned
+     *
+     * @return array Fields specific to this driver
+     */
+    function getRecDriverFields($type) {
+
+        return array();
+    }
+
+    /**
+     * Gets domains from driver for which the user has the specified
+     * permission.
+     *
+     * @param int $perms  Permissions filter for domain result set
+     *
+     * @return array  Possibly empty array of domain information
+     */
+    function getDomains($perms = PERMS_SHOW)
+    {
+        $domains = $this->_getDomains();
+        if (is_a($domains, 'PEAR_Error')) {
+            $GLOBALS['notification']->push($domains->getMessage() . ': ' . $domains->getDebugInfo(), 'horde.warning');
+            return array();
+        }
+
+        if (!Horde_Auth::isAdmin() &&
+            !$GLOBALS['perms']->hasPermission('beatnik:domains', Horde_Auth::getAuth(), $perms)) {
+            foreach ($domains as $id => $domain) {
+                if (!$GLOBALS['perms']->hasPermission('beatnik:domains:' . $domain['zonename'], Horde_Auth::getAuth(), $perms)) {
+                    unset($domains[$id]);
+                }
+            }
+        }
+
+        if (empty($domains)) {
+            $GLOBALS['notification']->push(_("You are not permitted to view any domains."), 'horde.warning');
+            return array();
+        }
+
+        // Sort the resulting list by domain name
+        // TODO: Allow sorting by other columns
+        require_once 'Horde/Array.php';
+        Horde_Array::arraySort($domains, 'zonename');
+
+        return $domains;
+    }
+
+    /**
+     * Return SOA for a single domain
+     *
+     * @param string $domain   Domain for which to return SOA information
+     *
+     * @return mixed           Array of SOA information for domain or PEAR_Error
+     *                         on failure.
+     */
+    function getDomain($domainname)
+    {
+        $domains = $this->getDomains(PERMS_SHOW | PERMS_READ);
+
+        foreach ($domains as $domain) {
+            if ($domain['zonename'] == $domainname) {
+                return $domain;
+            }
+        }
+        return PEAR::raiseError(sprintf(_("Unable to read requested domain %s"), $domainname));
+    }
+
+    /**
+     * Gets a specific record from the backend.  This method may be overridden
+     * in specific backend drivers if there is a performance or other benefit
+     * for doing so.
+     *
+     * @return array  Array of type and record information
+     */
+    function getRecord($id)
+    {
+        if ($id === null) {
+            return false;
+        }
+
+        $zonedata = $this->getRecords($_SESSION['beatnik']['curdomain']['zonename']);
+        // Search for the requested record id
+        foreach ($zonedata as $type => $records) {
+            foreach ($records as $record) {
+                if ($record['id'] == $id) {
+                    // Found the record we're looking for
+                    break;
+                }
+                $type = false;
+                $record = false;
+            }
+            if ($record) {
+                // Record found in nested loop.  Break out of this one too.
+                break;
+            }
+        }
+
+        if (!$record) {
+            // We may be editing the SOA.  See if it matches
+            $record = $this->getDomain($_SESSION['beatnik']['curdomain']['zonename']);
+            if ($record['id'] == $id) {
+                $type = 'soa';
+            } else {
+                $GLOBALS['notification']->push(_("Unable to locate requested record."), 'horde.error');
+                $type = false;
+                $record = false;
+            }
+        }
+
+        return array($type, $record);
+    }
+
+    /**
+     * Try to determinate if the autogenerated record
+     * already exits.  This method may be overridden in the backend driver
+     * for performance or other reasons.
+     *
+     * @param array $record record to check
+     *
+     * @return boolean if records exits or or not
+     */
+    function recordExists($record, $rectype)
+    {
+        $zonedata = $this->getRecords($_SESSION['beatnik']['curdomain']['zonename']);
+        if (is_a($zonedata, 'PEAR_Error')) {
+            $notification->push($zonedata, 'horde.error');
+            header('Location:' . Horde::applicationUrl('listzones.php'));
+            exit;
+        }
+
+        if (isset($zonedata[$rectype])) {
+            foreach ($zonedata[$rectype] as $row) {
+                // Prune empty values from $row to aid in comparison
+                foreach ($row as $key => $value) {
+                    if (empty($value)) {
+                        unset($row[$key]);
+                    }
+                }
+
+                $same_values = array_intersect_assoc($row, $record);
+                if (count($same_values) == count($record)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Saves a record fo the configured driver and checks/sets needCommit()
+     * Also first checks to ensure permission to save record is available.
+     *
+     * @param array $info  Data to be passed to backend driver for storage
+     *
+     * @return mixed  True on success, PEAR::Error on error
+     */
+    function saveRecord(&$info)
+    {
+        // Check to see if this is a new domain
+        if ($info['rectype'] == 'soa' && $info['zonename'] != $_SESSION['beatnik']['curdomain']['zonename']) {
+            // Make sure the user has permissions to add domains
+            if (!Beatnik::hasPermission('beatnik:domains', PERMS_EDIT)) {
+                return PEAR::raiseError(_('You do not have permission to create new domains.'));
+            }
+
+            // Create a dummy old domain for comparison purposes
+            $oldsoa['serial'] = 0;
+
+        } else {
+            $oldsoa =& $_SESSION['beatnik']['curdomain'];
+
+            // Check for permissions to edit the record in question
+            if ($info['rectype'] == 'soa') {
+                $node = 'beatnik:domains:' . $info['zonename'];
+                if (!Beatnik::hasPermission($node, PERMS_EDIT, 1)) {
+                    return PEAR::raiseError(_('You do not have permssion to edit the SOA of this zone.'));
+                }
+            } else {
+                $node = 'beatnik:domains:' . $_SESSION['beatnik']['curdomain']['zonename'] . ':' . $info['id'];
+                if (!Beatnik::hasPermission($node, PERMS_EDIT, 2)) {
+                    return PEAR::raiseError(_('You do not have permssion to edit this record.'));
+                }
+            }
+        }
+
+        // Save the changes to the backend
+        // FIXME: Modify saveRecord() to return the new (possibly changed) ID of the
+        // record and then use that ID to update permissions
+        $return = $this->_saveRecord($info);
+
+        $oldsoa =& $_SESSION['beatnik']['curdomain'];
+
+        if (is_a($return, 'PEAR_Error')) {
+            return $return;
+        } else {
+            if ($info['rectype'] == 'soa' &&
+               ($oldsoa['serial'] < $info['serial'])) {
+                // Clear the commit flag (if set)
+                Beatnik::needCommit($oldsoa['zonename'], false);
+            } else {
+                Beatnik::needCommit($oldsoa['zonename'], true);
+            }
+        }
+
+        // Check to see if an SOA was just added or updated.
+        // If so, make it the current domain.
+        if ($info['rectype'] == 'soa') {
+            $_SESSION['beatnik']['curdomain'] = $this->getDomain($info['zonename']);
+        }
+
+        return true;
+    }
+
+    /**
+     * Delete record from backend
+     *
+     * @access private
+     *
+     * @param array $info  Reference to array of record information for deletion
+     *
+     * @return boolean true on success, PEAR::raiseError on error
+     */
+    function deleteRecord(&$info)
+    {
+        $return = $this->_deleteRecord($info);
+
+        $oldsoa =& $_SESSION['beatnik']['curdomain'];
+
+        if (is_a($return, 'PEAR_Error')) {
+            return $return;
+        } else {
+            // No need to commit if the whole zone is gone
+            if ($info['rectype'] != 'soa') {
+                Beatnik::needCommit($oldsoa['zonename'], true);
+            }
+        }
+    }
+
+    /**
+     * Attempts to return a concrete Beatnik_Driver instance based on
+     * $driver.
+     *
+     * @param string $driver  The type of the concrete Beatnik_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 Beatnik_Driver instance, or
+     *                false on an error.
+     */
+    function factory($driver = null, $params = null)
+    {
+        if ($driver === null) {
+            $driver = $GLOBALS['conf']['storage']['driver'];
+        }
+
+        $driver = basename($driver);
+        if (empty($driver) || ($driver == 'none')) {
+            return new Horde_Lock();
+        }
+
+        if (is_null($params)) {
+            // Since we have more than one backend that uses SQL make sure
+            // all of them have a chance to inherit the site-wide config.
+            $sqldrivers = array('sql', 'pdnsgsql');
+            if (in_array($driver, $sqldrivers)) {
+                $params = Horde::getDriverConfig('storage', 'sql');
+            } else {
+                $params = Horde::getDriverConfig('storage', $driver);
+            }
+        }
+
+        require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
+        $class = 'Beatnik_Driver_' . $driver;
+        if (class_exists($class)) {
+            return new $class($params);
+        } else {
+            return PEAR::raiseError(_('Driver not found.'));
+        }
+    }
+
+}
diff --git a/beatnik/lib/Driver/ldap2dns.php b/beatnik/lib/Driver/ldap2dns.php
new file mode 100644 (file)
index 0000000..1ca8c63
--- /dev/null
@@ -0,0 +1,526 @@
+<?php
+/**
+ * $Horde: beatnik/lib/Driver/ldap2dns.php,v 1.25 2008/03/11 08:57:59 duck Exp $
+ *
+ * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Beatnik
+ */
+class Beatnik_Driver_ldap2dns extends Beatnik_Driver
+{
+    /**
+     * Handle for the current database connection.
+     * @var object LDAP $_LDAP
+     */
+    var $_LDAP;
+
+    /**
+     * Boolean indicating whether or not we're connected to the LDAP
+     * server.
+     * @var boolean $_connected
+     */
+    var $_connected = false;
+
+    /**
+    * Constructs a new Beatnik LDAP driver object.
+    *
+    * @param array  $params    A hash containing connection parameters.
+    */
+    function Beatnik_Driver_ldap2dns($params = array())
+    {
+        parent::Beatnik_Driver($params);
+        $this->_connect();
+    }
+
+    /**
+     * Get any record types  available specifically in this driver.
+     *
+     * @return array Records available only to this driver
+     */
+    function getRecDriverTypes()
+    {
+        return array(
+            'a+ptr' => 'A + PTR',
+        );
+    }
+
+    /**
+     * Get any fields available specifically in this driver by record type.
+     *
+     * @param string $type Record type for which fields should be returned
+     *
+     * @return array Fields specific to this driver
+     */
+    function getRecDriverFields($type) {
+        $recset = array();
+        switch($type) {
+        case 'a+ptr':
+            $recset['hostname'] = array(
+                'name' => 'Hostname',
+                'description' => 'Hostname',
+                'type' => 'text',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 1,
+            );
+            $recset['cipaddr'] = array(
+                'name' => 'IP Address',
+                'description' => 'IP Address to be forward and reverse mapped',
+                'type' => 'ipaddress',
+                'maxlength' => 0,
+                'required' => true,
+                'infoset' => 'basic',
+                'index' => 2,
+            );
+            break;
+        }
+
+        $recset['timestamp'] = array(
+            'name' => 'Timestamp',
+            'description' => '"Do Not Issue Before/After" Timestamp',
+            'type' => 'int',
+            'maxlength' => 0,
+            'required' => false,
+            'infoset' => 'advanced',
+            'index' => 100,
+        );
+        $recset['location'] = array(
+            'name' => 'Location',
+            'description' => 'Location Restriction',
+            'type' => 'text',
+            'maxlength' => 2,
+            'required' => false,
+            'infoset' => 'advanced',
+            'index' => 101,
+        );
+
+        return $recset;
+    }
+
+    /**
+     * Gets all zones for accessible to the user matching the filter
+     *
+     * @access private
+     *
+     * @return array Array with zone records numerically indexed
+     */
+    function _getDomains()
+    {
+        static $zonedata = array();
+        if (count($zonedata) > 0) {
+            # If at least one element is in the array then we should have valid
+            # cached data.
+            return $zonedata;
+        }
+
+        // Record format
+        // $zonedata =
+        //       zone array ( # numerically indexed
+        //                   zonename => zone domain name
+        //                   serial => zone SOA serial number
+        //                   refresh => zone SOA refresh
+        //                   retry => zone SOA retry
+        //                   expire => zone SOA expiry
+        //                   minimum => zone SOA minimum
+        //                   admin => zone contact admin
+        //                   zonemaster => SOA master NS
+        //           )
+
+        $res = ldap_list($this->_LDAP,
+            $this->_params['basedn'],
+            "(objectClass=dnszone)");
+
+        if ($res === false) {
+            return PEAR::raiseError("Unable to locate any DNS zones " .
+            "underneath ".$this->_params['basedn']);
+        }
+
+        $res = ldap_get_entries($this->_LDAP, $res);
+
+        if ($res === false) {
+            return PEAR::raiseError(sprintf(_("Unable to retrieve data from LDAP results: %s"), @ldap_error($this->_LDAP)));
+        }
+
+        $fields = Beatnik::getRecFields('soa');
+        $i = 0;
+        # FIXME: Add some way to handle missing zone data
+        # FIXME: Don't forget to remove error silencers (@whatever)
+        while ($i < $res['count']) {
+            $tmp = array();
+
+            foreach ($fields as $field => $fieldinfo) {
+                $key = strtolower($this->_getAttrByField($field));
+                if ($key === null) {
+                    // This is not a field we are concerned with.  Skip it
+                    continue;
+                }
+                // Special case for 'dn' as it's not treated as an array
+                if ($key == 'dn') {
+                    $val = @ldap_explode_dn($res[$i]['dn'], 1);
+                    $tmp[$field] = $val[0];
+                    continue;
+                }
+                @$tmp[$field] = $res[$i][$key][0];
+            }
+
+            # Push the zone on the stack
+            $zonedata[] = $tmp;
+
+            # Next zone, please
+            $i++;
+        }
+        return $zonedata;
+    }
+
+    /**
+     * Map LDAP Attributes to application record fields
+     *
+     * @access private
+     *
+     * @param $field string  LDAP Attribute for which a record field should be
+     *                       returned
+     *
+     * @return string  Application record field name
+     */
+    function _getAttrByField($field)
+    {
+        $field = strtolower($field);
+        $fields = array(
+            'hostname' => 'dnsdomainname',
+            'zonename' => 'dnszonename', # FIXME This will go away for ldap2dns 0.4.x
+            'serial' => 'dnsserial',
+            'refresh' => 'dnsrefresh',
+            'retry' => 'dnsretry',
+            'expire' => 'dnsexpire',
+            'minimum' => 'dnsminimum',
+            'zonecontact' => 'dnsadminmailbox',
+            'zonens' => 'dnszonemaster',
+            'ttl' => 'dnsttl',
+            'timestamp' => 'dnstimestamp',
+            'location' => 'dnslocation',
+            'ipaddr' => 'dnsipaddr',
+            'cipaddr' => 'dnscipaddr',
+            'pointer' => 'dnscname',
+            'pref' => 'dnspreference',
+            'priority' => 'dnssrvpriority',
+            'weight' => 'dnssrvweight',
+            'port' => 'dnssrvport',
+            'text' => 'dnscname', # FIXME THIS WILL CHANGE IN ldap2dns 0.5.0!!!
+            'id' => 'dn',
+        );
+
+        if (!isset($fields[$field])) {
+            return null;
+        }
+
+        return $fields[$field];
+
+    }
+
+    /**
+     * Gets all records associated with the given zone
+     *
+     * @param string $domain Retrieve records for this domain
+     *
+     * @return array Array with zone records
+     */
+    function getRecords($domain)
+    {
+        $domain = $this->cleanFilterString($domain);
+        $dn = $this->_params['dn'].'='.$domain.','.$this->_params['basedn'];
+        $res = @ldap_list($this->_LDAP, $dn, '(objectClass=dnsrrset)');
+
+        if ($res === false) {
+            return PEAR::raiseError("Unable to locate any DNS data for $domain");
+        }
+
+        # FIXME Cache these results
+        $zonedata = array();
+        $res = @ldap_get_entries($this->_LDAP, $res);
+        if ($res === false) {
+            return PEAR::raiseError(sprintf(_("Internal error: %s"), @ldap_error($this->_LDAP)));
+        }
+
+        $i = 0;
+        while ($i < $res['count']) {
+            $rectype = $res[$i]['dnstype'][0];
+            // Special case for A+PTR records
+            if ($rectype == 'a' && isset($res[$i]['dnscipaddr'])) {
+                $rectype = 'a+ptr';
+            }
+            if (!isset($zonedata[$rectype])) {
+                # Initialize this type if it hasn't already been done
+                $zonedata[$rectype] = array();
+            }
+            $tmp = array();
+            foreach (Beatnik::getRecFields($rectype) as $field => $fielddata) {
+                $key = $this->_getAttrByField($field);
+                if ($key === null) {
+                    // Not a key we care about
+                    continue;
+                }
+                // Special case for 'dn' as it's not treated as an array
+                if ($key == 'dn') {
+                    $val = @ldap_explode_dn($res[$i]['dn'], 1);
+                    $tmp[$field] = $val[0];
+                    continue;
+                }
+
+                // Only the first value is used.  All other are ignored.
+                $tmp[$field] = @$res[$i][$key][0];
+            }
+            # Push the record on the stack
+            $zonedata[$rectype][] = $tmp;
+
+            # Next entry, please.
+            $i++;
+        }
+
+        return $zonedata;
+    }
+
+    /**
+     * Delete record from backend
+     *
+     * @access private
+     *
+     * @param array $info  Reference to array of record information for deletion
+     *
+     * @return boolean true on success, PEAR::raiseError on error
+     */
+    function _deleteRecord(&$info)
+    {
+        // Ensure we have a record ID before continuing
+        if (!isset($info['id'])) {
+            return PEAR::raiseError(_("Unable to delete record: No record ID specified."));
+        }
+
+        // Attribute used to identify objects
+        $dnattr = $this->_params['dn'];
+
+        $suffix = $dnattr . '=' . $_SESSION['beatnik']['curdomain']['zonename'] . ',' . $this->_params['basedn'];
+        if ($info['rectype'] == 'soa') {
+            // FIXME: Add recursion
+            return PEAR::raiseError(_("Unsupported recursive delete."));
+
+            $domain = $this->cleanDNString($info['zonename']);
+            $dn = $suffix;
+        } else {
+            $domain = $this->cleanDNString($_SESSION['beatnik']['curdomain']['zonename']);
+            // Strip the array fluff and add the attribute
+            $dn = $dnattr . '=' . $this->cleanDNString($info['id']) . ',' . $suffix;
+        }
+
+        $res = @ldap_delete($this->_LDAP, $dn);
+        if ($res === false) {
+            return PEAR::raiseError(sprintf(_("Unable to delete record.  Reason: %s"), @ldap_error($this->_LDAP)));
+        }
+        return true;
+    }
+
+    /**
+     * Saves a new or edited record to the DNS backend
+     *
+     * @access private
+     *
+     * @param array $info Array from Horde_Form with record data
+     *
+     * @return mixed  The new or modified record ID on success;
+     *                PEAR_Error on error
+     */
+    function _saveRecord($info)
+    {
+        // Make sure we have a valid record type
+        $rectype = strtolower($info['rectype']);
+        $rdata = false;
+        foreach (Beatnik::getRecTypes() as $rtype => $rdata) {
+            if ($rectype == $rtype) {
+                break;
+            }
+            $rdata = false;
+        }
+
+        if (!$rdata) {
+            return PEAR::raiseError(_("Invalid record type specified."));
+        }
+
+        $recfields = Beatnik::getRecFields($rectype);
+
+        $entry = array();
+
+        if ($rectype == 'a+ptr') {
+            // Special case for A+PTR Records
+            $entry['dnstype'] = 'a';
+        } else {
+            $entry['dnstype'] = $rectype;
+        }
+
+        $id = strtoupper($rectype);
+
+        // Apply each piece of submitted data to the new/updated object
+        foreach ($recfields as $field => $fdata) {
+            // Translate the key to an LDAP attribute
+            $key = $this->_getAttrByField($field);
+
+            if ($key === null || $key == 'dn') {
+                // Skip the DN or any other key we don't care about
+                continue;
+            }
+
+            if (!isset($info[$field]) && isset($fdata['default'])) {
+                // No value specified.  Use the default
+                $val = $fdata['default'];
+            } else {
+                // Only populate the field if there is actual data
+                if (isset($info[$field]) && strlen($info[$field])) {
+                    $entry[$key] = $info[$field];
+                } else {
+                    // $info[$field] was possibly unset
+                    $info[$field] = '';
+                    // If the record previously had data, we have to send an
+                    // empty array to remove the attribute.  However, always
+                    // sending an empty attribute causes PHP to return with
+                    // "Protocol Error".  Hence this somewhat expensive check:
+                    if (isset($info['id'])) {
+                        list($type, $record) = $this->getRecord($info['id']);
+                        if ($record && isset($record[$field])) {
+                            $entry[$key] = array();
+                        }
+                    }
+                }
+            }
+
+            if (!isset($entry[$key]) && $fdata['required']) {
+                // No value available but required field
+                return PEAR::raiseError(sprintf(_("Missing required field %s to save record."), $fdata['name']));
+            }
+
+            // Construct an ID for this object as a tuple of its data.
+            // This guarantees uniqueness.
+            $id .= '-'.$this->cleanDNString($info[$field]);
+        }
+
+        // Create and populate the DN
+        $key = $this->_params['dn'];
+        $dn = '';
+        // Special case for SOA records.
+        if ($rectype == 'soa') {
+            $domain = $this->cleanDNString($info['zonename']);
+            $entry[$key] = $domain;
+            $id = $domain;
+            $dn = $key.'='.$domain;
+            $suffix = $this->_params['basedn'];
+        } else {
+            // Everything else gets full id for DN
+            $id = $this->cleanDNString($id);
+            $entry[$key] = $id;
+            $dn = $key.'='.$id;
+            // The domain is held in the session
+            $domain = $this->cleanDNString($_SESSION['beatnik']['curdomain']['zonename']);
+            // Prepare the DN suffix
+            $suffix = $key.'='.$domain.','.$this->_params['basedn'];
+        }
+
+        // Check to see if this is a modification
+        if (isset($info['id'])) {
+            // Get the base name of the old object
+            $oldRDN = $key . '=' . $this->cleanDNString($info['id']);
+            if ($dn != $oldRDN) {
+                // We have an old DN but it doesn't match the new DN.
+                // Need to rename the old object
+                if ($rectype == 'soa') {
+                    return PEAR::raiseError(_("Unsupported operation: cannot rename a domain."));
+                }
+                $res = @ldap_rename($this->_LDAP, $oldRDN . ',' . $suffix,
+                    $dn, $suffix, true);
+                if ($res === false) {
+                    return PEAR::raiseError(sprintf(_("Unable to rename old object.  Reason: %s"), @ldap_error($this->_LDAP)));
+                }
+            }
+
+            // Finish appending the DN suffix information
+            $dn .= ',' . $suffix;
+
+            // Modify the existing record
+            $res = @ldap_mod_replace($this->_LDAP, $dn, $entry);
+            if ($res === false) {
+                return PEAR::raiseError(sprintf(_("Unable to modify record.  Reason: %s"), @ldap_error($this->_LDAP)));
+            }
+
+        } else {
+            // Must be a new record
+            // Append the suffix to the DN to make it fully qualified
+            $dn .= ',' . $suffix;
+            // Create the necessary objectClass definitions
+            $entry['objectclass'] = array();
+            $entry['objectclass'][] = 'top';
+            $entry['objectclass'][] = 'dnszone';
+            if ($rectype != 'soa') {
+                // An objectclass to hold the non-SOA record information
+                $entry['objectclass'][] = 'dnsrrset';
+            }
+            $res = @ldap_add($this->_LDAP, $dn, $entry);
+            if ($res === false) {
+                return PEAR::raiseError(sprintf(_("Unable to add record to LDAP. Reason: %s"), @ldap_error($this->_LDAP)));
+            }
+        }
+
+        return $id;
+    }
+
+    function cleanFilterString($string) {
+        return preg_replace(
+            array('/\*/',   '/\(/',   '/\)/',   '/\x00/'),
+            array('\2a', '\28', '\29', '\00'),
+            $string
+        );
+    }
+
+    function cleanDNString($string) {
+        return preg_replace(
+            array('/=/', '/,/', '/\+/'),
+            array('-', '~', ''),
+            $string);
+    }
+
+    /**
+     * Attempts to open a connection to the LDAP server.
+     *
+     * @access private
+     *
+     * @return boolean    True on success; exits (Horde::fatal()) on error.
+     *
+     * @access private
+     */
+    function _connect()
+    {
+        if (!$this->_connected) {
+            Horde::assertDriverConfig($this->_params, 'storage',
+                array('hostspec', 'basedn', 'binddn', 'password', 'dn'));
+
+            $port = (isset($this->_params['port'])) ?
+                $this->_params['port'] : 389;
+
+            $this->_LDAP = ldap_connect($this->_params['hostspec'], $port);
+            if (!$this->_LDAP) {
+                Horde::fatal("Unable to connect to LDAP server $hostname on $port", __FILE__, __LINE__);
+            }
+            $res = ldap_set_option($this->_LDAP, LDAP_OPT_PROTOCOL_VERSION, $this->_params['version']);
+            if ($res === false) {
+                return PEAR::raiseError("Unable to set LDAP protocol version");
+            }
+            $res = ldap_bind($this->_LDAP, $this->_params['binddn'], $this->_params['password']);
+            if ($res === false) {
+                return PEAR::raiseError("Unable to bind to the LDAP server. Check authentication credentials.");
+            }
+
+            $this->_connected = true;
+        }
+        return true;
+    }
+}
diff --git a/beatnik/lib/Driver/pdnsgsql.php b/beatnik/lib/Driver/pdnsgsql.php
new file mode 100644 (file)
index 0000000..0fb90db
--- /dev/null
@@ -0,0 +1,518 @@
+<?php
+/**
+ * The Beatnik_Driver_sql class implements a SQL driver for managing DNS records
+ * in the PowerDNS generic SQL driver.  The PowerDNS generic SQL driver aims to
+ * support MySQL, PostgreSQL, SQLite and Oracle.  This driver attempts to do the
+ * same as long as the default queries are used.
+ *
+ * $Horde: beatnik/lib/Driver/pdnsgsql.php,v 1.4 2009/07/03 10:05:30 duck Exp $
+ *
+ * Copyright 2008 The Horde Project <http://www.horde.org>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Beatnik
+ */
+
+class Beatnik_Driver_pdnsgsql extends Beatnik_Driver
+{
+    /**
+     * Hash containing connection parameters.
+     *
+     * @var array
+     */
+    var $_params = array();
+
+    /**
+     * Handle for the current database connection.
+     *
+     * @var DB
+     */
+    var $_db;
+
+    /**
+     * Handle for the current database connection, used for writing. Defaults
+     * to the same handle as $_db if a separate write database is not required.
+     *
+     * @var DB
+     */
+    var $_write_db;
+
+    /**
+     * Boolean indicating whether or not we're connected to the SQL server.
+     *
+     * @var boolean
+     */
+    var $_connected = false;
+
+    /**
+    * Constructs a new Beatnik DB driver object.
+    *
+    * @param array  $params    A hash containing connection parameters.
+    */
+    function Beatnik_Driver_pdnsgsql($params = array())
+    {
+        parent::Beatnik_Driver($params);
+    }
+
+    /**
+     * Gets all zones
+     *
+     * @access private
+     *
+     * @return array Array with zone records numerically indexed
+     */
+    function _getDomains()
+    {
+       if (is_a(($result = $this->_connect()), 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(_("Internal database error.  Details have been logged for the administrator."));
+        }
+
+        $query = 'SELECT d.id, d.name AS name, r.content AS content, ' .
+                 'r.ttl AS ttl FROM ' . $this->_params['domains_table'] .
+                 ' AS d JOIN ' . $this->_params['records_table'] . ' AS r ON ' .
+                 'r.domain_id = d.id WHERE r.type = \'SOA\'';
+        Horde::logMessage('SQL Query by Beatnik_Driver_pdnsgsql::_getDomains(): ' .  $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $domainlist =  $this->_db->getAll($query, null, DB_FETCHMODE_ASSOC);
+        if (is_a($domainlist, 'PEAR_Error')) {
+            Horde::logMessage($domainlist, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return array();
+        }
+
+        $results = array();
+        foreach ($domainlist as $info) {
+            $soa = explode(' ', $info['content']);
+            if (count($soa) != 7) {
+                Horde::logMessage(sprintf('Invalid SOA found for %s, skipping.', $info['name']), __FILE__, __LINE__, PEAR_LOG_WARNING);
+                continue;
+            }
+
+            $d = array();
+            $d['id'] = $info['id'];
+            $d['zonename'] = $info['name'];
+            $d['zonemaster'] = $d['zonens'] = $soa[0];
+            $d['admin'] = $d['zonecontact'] = $soa[1];
+            $d['serial'] = $soa[2];
+            $d['refresh'] = $soa[3];
+            $d['retry'] = $soa[4];
+            $d['expire'] = $soa[5];
+            $d['minimum'] = $soa[6];
+            $results[] = $d;
+        }
+
+        return $results;
+    }
+
+    /**
+     * Return SOA for a single domain
+     *
+     * @param string $domain   Domain for which to return SOA information
+     *
+     * @return array           Domain SOA
+     */
+    function getDomain($domainname)
+    {
+       if (is_a(($result = $this->_connect()), 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(_("Internal database error.  Details have been logged for the administrator."));
+        }
+
+        $query = 'SELECT d.id AS id, d.name AS name, r.content AS content, ' .
+                 'r.ttl AS ttl FROM ' . $this->_params['domains_table'] .
+                 ' AS d JOIN ' . $this->_params['records_table'] . ' AS r ON ' .
+                 'r.domain_id = d.id WHERE r.type = \'SOA\' AND d.name = ?';
+        $values = array($domainname);
+        Horde::logMessage('SQL Query by Beatnik_Driver_pdnsgsql::getDomain(): ' .  $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result =  $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(_("An error occurred while searching the database.  Details have been logged for the administrator."), __FILE__, __LINE__, PEAR_LOG_ERR);
+        }
+
+        if (count($result) != 1) {
+            return PEAR::raiseError(_("Too many domains matched that name.  Contact your administrator."));
+        }
+
+        $info = $result[0];
+
+        $soa = explode(' ', $info['content']);
+        if (count($soa) != 7) {
+            Horde::logMessage(sprintf('Invalid SOA found for %s, skipping.', $info['name']), __FILE__, __LINE__, PEAR_LOG_WARN);
+            return PEAR::raiseError(_("Corrupt SOA found for zone.  Contact your administrator."), __FILE__, __LINE__, PEAR_LOG_ERR);
+        }
+
+        $ret = array();
+        $ret['id'] = $info['id'];
+        $ret['zonename'] = $info['name'];
+        $ret['zonemaster'] = $soa[0];
+        $ret['admin'] = $soa[1];
+        $ret['serial'] = $soa[2];
+        $ret['refresh'] = $soa[3];
+        $ret['retry'] = $soa[4];
+        $ret['expire'] = $soa[5];
+        $ret['minimum'] = $soa[6];
+
+        return $ret;
+    }
+
+    /**
+     * Gets all records associated with the given zone
+     *
+     * @param string $domain Retrieve records for this domain
+     *
+     * @return array Array with zone records
+     */
+    function getRecords($domain)
+    {
+       if (is_a(($result = $this->_connect()), 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(_("Internal database error.  Details have been logged for the administrator."));
+        }
+
+        $zonedata = array();
+
+        $query = 'SELECT d.id AS domain_id, r.id AS id, d.name AS domain, ' .
+                 'r.name AS name, r.type AS type, r.content AS content, ' .
+                 'r.ttl AS ttl, r.prio AS prio FROM ' .
+                  $this->_params['domains_table'] . ' AS d JOIN ' .
+                  $this->_params['records_table'] . ' AS r ON ' .
+                  'd.id = r.domain_id AND d.name = ?';
+        $values = array($domain);
+
+        Horde::logMessage('SQL Query by Beatnik_Driver_pdnsgsql::getRecords(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+        $result = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(_("An error occurred while searching the database.  Details have been logged for the administrator."), __FILE__, __LINE__, PEAR_LOG_ERR);
+        }
+
+        foreach ($result as $rec) {
+            $type = strtolower($rec['type']);
+            if (!isset($zonedata[$type])) {
+                $zonedata[$type] = array();
+            }
+
+            $tmp = array();
+            $tmp['id'] = $rec['id'];
+            $tmp['ttl'] = $rec['ttl'];
+            switch($type) {
+            case 'soa':
+                $soa = explode(' ', $rec['content']);
+                if (count($soa) != 7) {
+                    Horde::logMessage(sprintf('Invalid SOA found for %s, skipping.', $info['name']), __FILE__, __LINE__, PEAR_LOG_WARNING);
+                }
+        
+                $tmp['zonename'] = $rec['name'];
+                $tmp['zonens'] = $soa[0];
+                $tmp['zonecontact'] = $soa[1];
+                $tmp['serial'] = $soa[2];
+                $tmp['refresh'] = $soa[3];
+                $tmp['retry'] = $soa[4];
+                $tmp['expire'] = $soa[5];
+                $tmp['minimum'] = $soa[6];
+                break;
+
+            case 'a':
+                $tmp['hostname'] = $rec['name'];
+                $tmp['ipaddr'] = $rec['content'];
+                break;
+
+            case 'ptr':
+                $tmp['hostname'] = $rec['name'];
+                $tmp['pointer'] = $rec['content'];
+                break;
+
+            case 'mx':
+                $tmp['pointer'] = $rec['content'];
+                $tmp['pref'] = $rec['prio'];
+                break;
+
+            case 'cname':
+                $tmp['hostname'] = $rec['name'];
+                $tmp['pointer'] = $rec['content'];
+                break;
+
+            case 'ns':
+                $tmp['hostname'] = $rec['name'];
+                $tmp['pointer'] = $rec['content'];
+                break;
+
+            case 'srv':
+                $srv = preg_split('/\s+/', trim($rec['content']));
+                if (count($srv) != 3) {
+                    Horde::logMessage(sprintf('Invalid SRV data found for %s, skipping.', $rec['name']), __FILE__, __LINE__, PEAR_LOG_WARNING);
+                    continue;
+                }
+                $tmp['hostname'] = $rec['name'];
+                $tmp['weight'] = $srv[0];
+                $tmp['port'] = $srv[1];
+                $tmp['pointer'] = $srv[2];
+                $tmp['priority'] = $rec['prio'];
+                break;
+
+            case 'txt':
+                $tmp['hostname'] = $rec['name'];
+                $tmp['text'] = $rec['content'];
+                break;
+            }
+
+            $zonedata[$type][] = $tmp;
+        }
+
+        return $zonedata;
+    }
+
+    /**
+     * Saves a new or edited record to the DNS backend
+     *
+     * @access private
+     *
+     * @param array $info Array of record data
+     *
+     * @return boolean true on success, PEAR::raiseError on error
+     */
+    function _saveRecord($info)
+    {
+       if (is_a(($result = $this->_connect()), 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(_("Internal database error.  Details have been logged for the administrator."));
+        }
+
+        $change_date = time();
+        $domain_id = $_SESSION['beatnik']['curdomain']['id'];
+
+        switch($info['rectype']) {
+        case 'soa':
+            if (empty($info['refresh'])) {
+                // 24 hours
+                $info['refresh'] = 86400;
+            }
+            if (empty($info['retry'])) {
+               // 2 hours
+               $info['retry'] = 7200;
+            }
+            if (empty($info['expire'])) {
+               // 1000 hours
+               $info['expire'] = 3600000;
+            }
+            if (empty($info['minimum'])) {
+               // 2 days
+               $info['miniumum'] = 172800;
+            }
+
+            $name = $info['zonename'];
+            $type = 'SOA';
+            $content = $info['zonens'] . ' ' . $info['zonecontact'] . ' ' .
+                       $info['serial'] . ' ' . $info['refresh'] . ' ' .
+                       $info['retry'] . ' ' . $info['expire'] . ' ' .
+                       $info['minimum'];
+            $ttl = $info['ttl'];
+            $prio = null;
+            break;
+
+        case 'a':
+            $name = $info['hostname'];
+            $type = 'A';
+            $content = $info['ipaddr'];
+            $ttl = $info['ttl'];
+            $prio = null;
+            break;
+
+        case 'ptr':
+            $name = $info['hostname'];
+            $type = 'PTR';
+            $content = $info['pointer'];
+            $ttl = $info['ttl'];
+            $prio = null;
+            break;
+
+        case 'mx':
+            $name = $_SESSION['beatnik']['curdomain']['zonename'];
+            $type = 'MX';
+            $content = $info['pointer'];
+            $ttl = $info['ttl'];
+            $prio = $info['pref'];
+            break;
+
+        case 'cname':
+            $name = $info['hostname'];
+            $type = 'CNAME';
+            $content = $info['pointer'];
+            $ttl = $info['ttl'];
+            $prio = null;
+            break;
+
+        case 'ns':
+            $name = $info['hostname'];
+            $type = 'NS';
+            $content = $info['pointer'];
+            $ttl = $info['ttl'];
+            $prio = null;
+            break;
+
+        case 'srv':
+            $name = $info['hostname'];
+            $type = 'SRV';
+            $content = $info['weight'] . ' ' . $info['port'] . ' ' .
+                       $info['pointer'];
+            $ttl = $info['ttl'];
+            $prio = $info['priority'];
+            break;
+
+        case 'txt':
+            $name = $info['hostname'];
+            $type = 'TXT';
+            $content = $info['text'];
+            $ttl = $info['ttl'];
+            $prio = null;
+            break;
+        }
+
+        if (!empty($info['id'])) {
+            $query = 'UPDATE ' . $this->_params['records_table'] . ' SET ' . 
+                     'name = ?, type = ?, content = ?, ttl = ?, ' .
+                     'prio = ' . (empty($prio) ? 'NULL' : $prio) . ', ' .
+                     'change_date = ? WHERE id = ?';
+            $values = array($name, $type, $content, $ttl);
+            if (!empty($prio)) {
+                $values[] = $prio;
+            }
+            $values[] = $change_date;
+            $values[] = $info['id'];
+        } else {
+            $query = 'INSERT INTO ' . $this->_params['records_table'] . ' ' .
+                     '(domain_id, name, type, content, ttl, prio, ' . 
+                     'change_date) VALUES (?, ?, ?, ?, ?, ' .
+                     (empty($prio) ? 'NULL' : '?') . ', ?)';
+            $values = array($domain_id, $name, $type, $content, $ttl);
+            if (!empty($prio)) {
+                $values[] = $prio;
+            }
+            $values[] = $change_date;
+        }
+
+        Horde::logMessage('SQL Query by Beatnik_Driver_pdnsgsql::_saveRecord(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+        $result = $this->_write_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        return true;
+    }
+
+    /**
+     * Delete record from backend
+     *
+     * @access private
+     *
+     * @param array $data  Reference to array of record data to be deleted
+     *
+     * @return boolean true on success, PEAR::Error on error
+     */
+    function _deleteRecord($data)
+    {
+       if (is_a(($result = $this->_connect()), 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(_("Internal database error.  Details have been logged for the administrator."));
+        }
+
+        return PEAR::raiseError(_("Not implemented."));
+    }
+
+    /**
+     * Attempts to open a persistent connection to the SQL server.
+     *
+     * @access private
+     *
+     * @return boolean  True on success; exits (Horde::fatal()) on error.
+     */
+    function _connect()
+    {
+        if ($this->_connected) {
+            return true;
+        }
+
+        $result = Horde_Util::assertDriverConfig($this->_params, array('phptype'),
+                                           'PowerDNS Generic SQL',
+                                           array('driver' => 'pdnsgsql'));
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        if (!isset($this->_params['domains_table'])) {
+            $this->_params['domains_table'] = 'domains';
+        }
+        if (!isset($this->_params['records_table'])) {
+            $this->_params['records_table'] = 'records';
+        }
+
+        /* Connect to the SQL server using the supplied parameters. */
+        require_once 'DB.php';
+        $this->_write_db = &DB::connect($this->_params,
+                                        array('persistent' => !empty($this->_params['persistent'])));
+        if (is_a($this->_write_db, 'PEAR_Error')) {
+            Horde::fatal($this->_write_db, __FILE__, __LINE__);
+        }
+
+        // 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 (is_a($this->_db, 'PEAR_Error')) {
+                Horde::fatal($this->_db, __FILE__, __LINE__);
+            }
+
+            // 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;
+
+        return true;
+    }
+
+    /**
+     * Disconnects from the SQL server and cleans up the connection.
+     *
+     * @access private
+     *
+     * @return boolean  True on success, false on failure.
+     */
+    function _disconnect()
+    {
+        if ($this->_connected) {
+            $this->_connected = false;
+            $this->_db->disconnect();
+            $this->_write_db->disconnect();
+        }
+
+        return true;
+    }
+
+}
diff --git a/beatnik/lib/Driver/sql.php b/beatnik/lib/Driver/sql.php
new file mode 100644 (file)
index 0000000..4676ac1
--- /dev/null
@@ -0,0 +1,284 @@
+<?php
+/**
+ * $Horde: beatnik/lib/Driver/sql.php,v 1.21 2008/08/20 08:56:53 duck Exp $
+ *
+ * Copyright 2006-2008 The Horde Project (http://www.horde.org/)
+ *
+ * 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 Duck <duck@obala.net>
+ * @package Beatnik
+ */
+
+class Beatnik_Driver_sql extends Beatnik_Driver
+{
+    /**
+     * Hash containing connection parameters.
+     *
+     * @var array
+     */
+    var $_params = array();
+
+    /**
+     * Handle for the current database connection.
+     *
+     * @var DB
+     */
+    var $_db;
+
+    /**
+     * Handle for the current database connection, used for writing. Defaults
+     * to the same handle as $_db if a separate write database is not required.
+     *
+     * @var DB
+     */
+    var $_write_db;
+
+    /**
+     * Boolean indicating whether or not we're connected to the SQL server.
+     *
+     * @var boolean
+     */
+    var $_connected = false;
+
+    /**
+    * Constructs a new Beatnik DB driver object.
+    *
+    * @param array  $params    A hash containing connection parameters.
+    */
+    function Beatnik_Driver_sql($params = array())
+    {
+        parent::Beatnik_Driver($params);
+        $this->_connect();
+    }
+
+    /**
+     * Get any record types  available specifically in this driver.
+     *
+     * @return array Records available only to this driver
+     */
+    function getRecDriverTypes()
+    {
+        return array();
+    }
+
+
+    /**
+     * Get any fields available specifically in this driver by record type.
+     *
+     * @param string $type Record type for which fields should be returned
+     *
+     * @return array Fields specific to this driver
+     */
+    function getRecDriverFields($type) {
+
+        return array();
+    }
+
+    /**
+     * Gets all zones
+     *
+     * @access private
+     *
+     * @return array Array with zone records numerically indexed
+     */
+    function _getDomains()
+    {
+        $query = 'SELECT * FROM beatnik_soa ORDER BY zonename';
+        return $this->_db->getAll($query, null, DB_FETCHMODE_ASSOC);
+    }
+
+    /**
+     * Return SOA for a single domain
+     *
+     * @param string $domain   Domain for which to return SOA information
+     *
+     * @return array           Domain SOA
+     */
+    function getDomain($domainname)
+    {
+        $query = 'SELECT * FROM beatnik_soa WHERE zonename = ? ORDER BY zonename';
+        return $this->_db->getRow($query, array($domainname), DB_FETCHMODE_ASSOC);
+    }
+
+    /**
+     * Gets all records associated with the given zone
+     *
+     * @param string $domain Retrieve records for this domain
+     *
+     * @return array Array with zone records
+     */
+    function getRecords($domain)
+    {
+        $zonedata = array();
+        $params = array($domain);
+
+        foreach (array_keys(Beatnik::getRecTypes()) as $type) {
+            if ($type == 'soa') {
+                continue;
+            }
+            if ($type == 'mx') {
+                $order = 'pointer';
+            } else {
+                $order = 'hostname';
+            }
+
+            $query = 'SELECT * FROM beatnik_' . $type . ' WHERE zonename = ? ORDER BY ' .  $order . ' ASC';
+            $result = $this->_db->getAll($query, $params, DB_FETCHMODE_ASSOC);
+            if (is_a($result, 'PEAR_Error') || empty($result)) {
+                continue;
+            }
+
+            $zonedata[$type] = $result;
+        }
+
+        return $zonedata;
+    }
+
+    /**
+     * Saves a new or edited record to the DNS backend
+     *
+     * @access private
+     *
+     * @param array $info Array of record data
+     *
+     * @return boolean true on success, PEAR::raiseError on error
+     */
+    function _saveRecord($info)
+    {
+        $fields = array_keys(Beatnik::getRecFields($info['rectype']));
+        $params = array();
+        foreach ($fields as $i => $key) {
+            if (!isset($info[$key])) {
+                unset($fields[$i]);
+                continue;
+            }
+            $params[$key] = $info[$key];
+        }
+
+        if (isset($params['id']) && $params['id']) {
+            unset($params['id'], $fields[0]);
+            $query = 'UPDATE beatnik_' . $info['rectype'] . ' SET ';
+            foreach ($fields as $key) {
+                $query .= $key . ' = ?, ';
+                $params[$key] = $info[$key];
+            }
+            $query = substr($query, 0, -2) . ' WHERE id = ?';
+            $params['id'] = $info['id'];
+        } else {
+            unset($params['id'], $fields[0]);
+            if ($info['rectype'] != 'soa') {
+                $fields[] = 'zonename';
+                $params['zonename'] =  $_SESSION['beatnik']['curdomain']['zonename'];
+            }
+            $query = 'INSERT INTO beatnik_' . $info['rectype'] . ' (' . implode(', ', $fields) . ') ' . 
+                     ' VALUES (' . substr(str_repeat('?, ', sizeof($params)), 0, -2) . ')';
+        }
+
+        return $this->_write_db->query($query, $params);
+    }
+
+    /**
+     * Delete record from backend
+     *
+     * @access private
+     *
+     * @param array $data  Reference to array of record data to be deleted
+     *
+     * @return boolean true on success, PEAR::Error on error
+     */
+    function _deleteRecord($data)
+    {
+        // delete just one record
+        if ($data['rectype'] != 'soa') {
+            return $this->_write_db->query('DELETE FROM beatnik_' . $data['rectype'] . ' WHERE id = ?', array($data['id']));
+        }
+
+        // delete all subrecords
+        $params = array($data['curdomain']);
+        foreach (array_keys(Beatnik::getRecTypes()) as $type) {
+            if ($type == 'soa') {
+                continue;
+            }
+            $result = $this->_write_db->query('DELETE FROM beatnik_' . $type . ' WHERE zonename = ?', $params);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        // we are cuccesfull so, delete even soa
+        return $this->_write_db->query('DELETE FROM beatnik_soa WHERE zonename = ?', $params);
+    }
+
+    /**
+     * Attempts to open a persistent connection to the SQL server.
+     *
+     * @access private
+     *
+     * @return boolean  True on success; exits (Horde::fatal()) on error.
+     */
+    function _connect()
+    {
+        if ($this->_connected) {
+            return true;
+        }
+
+        Horde::assertDriverConfig($this->_params, 'storage',
+                                  array('phptype', 'charset'));
+
+        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. */
+        require_once 'DB.php';
+        $this->_write_db = &DB::connect($this->_params,
+                                        array('persistent' => !empty($this->_params['persistent'])));
+        if (is_a($this->_write_db, 'PEAR_Error')) {
+            Horde::fatal($this->_write_db, __FILE__, __LINE__);
+        }
+
+        // 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 (is_a($this->_db, 'PEAR_Error')) {
+                Horde::fatal($this->_db, __FILE__, __LINE__);
+            }
+
+            // 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;
+
+        return true;
+    }
+}
diff --git a/beatnik/lib/Forms/Autogenerate.php b/beatnik/lib/Forms/Autogenerate.php
new file mode 100644 (file)
index 0000000..735192d
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * $Horde: beatnik/lib/Forms/Autogenerate.php,v 1.6 2009/07/03 10:05:30 duck Exp $
+ *
+ * Copyright 2006-2007 Alkaloid Networks <http://www.alkaloid.net>
+ *
+ * See the enclosed file LICENSE for license information (GPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/gpl.php.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Beatnik
+ */
+class Autogenerate extends Horde_Form
+{
+    /**
+     */
+    function Autogenerate(&$vars)
+    {
+        require BEATNIK_BASE . '/config/autogenerate.php';
+
+        parent::Horde_Form($vars, _("Choose a template for autogenerating the records:"), 'autogenerate');
+        $this->setButtons(array(_("Autogenerate"), _("Cancel")));
+
+        // Create an array of template => description for the enum
+        $template_keys = array_keys($templates);
+        foreach ($template_keys as $template) {
+            $t[$template] = $templates[$template]['description'];
+        }
+        $this->addVariable(_("Template"), 'template', 'enum', true, false, null, array($t, true));
+
+        return true;
+    }
+}
diff --git a/beatnik/lib/Forms/DeleteRecord.php b/beatnik/lib/Forms/DeleteRecord.php
new file mode 100644 (file)
index 0000000..7db5376
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * $Horde: beatnik/lib/Forms/DeleteRecord.php,v 1.5 2009/07/03 10:05:30 duck Exp $
+ *
+ * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
+ *
+ * See the enclosed file LICENSE for license information (GPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/gpl.php.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Beatnik
+ */
+class DeleteRecord extends Horde_Form
+{
+    /**
+     */
+    function DeleteRecord(&$vars)
+    {
+        parent::Horde_Form($vars, _("Are you sure you want to delete this record?"));
+
+        $rectype = $vars->get('rectype');
+        $types = Beatnik::getRecTypes();
+
+        $this->addHidden('', 'id', 'text', $vars->get('id'));
+        $this->addHidden('', 'curdomain', 'text', $vars->get('curdomain'));
+        $this->addHidden('', 'rectype', 'text', $vars->get('rectype'));
+        $this->addVariable(_("Type"), 'rectype', 'text', false, true);
+
+        $recset = Beatnik::getRecFields($rectype);
+        foreach ($recset as $field => $fdata) {
+            if ($fdata['type'] != 'hidden' && ($fdata['infoset'] == 'basic' || $_SESSION['beatnik']['expertmode'])) {
+                $this->addVariable(_($fdata['description']), $field, $fdata['type'], false, true);
+            }
+
+        }
+
+        $this->setButtons(array(_("Delete"), _("Cancel")));
+
+        return true;
+    }
+}
diff --git a/beatnik/lib/Forms/EditRecord.php b/beatnik/lib/Forms/EditRecord.php
new file mode 100644 (file)
index 0000000..c3b7888
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ * $Horde: beatnik/lib/Forms/EditRecord.php,v 1.8 2009/07/03 10:05:30 duck Exp $
+ *
+ * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
+ *
+ * See the enclosed file LICENSE for license information (GPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/gpl.php.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Beatnik
+ */
+class EditRecord extends Horde_Form
+{
+    /**
+     */
+    function EditRecord(&$vars)
+    {
+        $isnew = !$vars->exists('id');
+        $rectype = $vars->get('rectype');
+        $recset = Beatnik::getRecFields($rectype);
+        if ($isnew) {
+            // Pre-load the field defaults on a new record
+            foreach ($recset as $field => $fdata) {
+                if (isset($fdata['default'])) {
+                    $vars->set($field, $fdata['default']);
+                }
+            }
+        }
+
+        parent::Horde_Form($vars, $isnew ? _("Add DNS Record") : _("Edit DNS Record"));
+
+        $types = Beatnik::getRecTypes();
+        if (empty($_SESSION['beatnik']['curdomain'])) {
+            // Without an active domain, limit the form to creating a new zone.
+            $types = array('soa' => _('SOA (Start of Authority)'));
+        }
+        $action = &Horde_Form_Action::factory('reload');
+        $select = &$this->addVariable(_("Record Type"), 'rectype', 'enum', true,
+                                             false, null, array($types, true));
+        $select->setAction($action);
+        $select->setOption('trackchange', true);
+
+        // Do not show record-specific fields until a record type is chosen
+        if (!$rectype) {
+            return true;
+        }
+
+        foreach ($recset as $field => $fdata) {
+            if ($fdata['type'] == 'hidden' || ($fdata['infoset'] != 'basic' &&
+                           !$_SESSION['beatnik']['expertmode'])) {
+                $this->addHidden(_($fdata['description']), $field, 'text',
+                                                $fdata['required']);
+            } else {
+                $this->addVariable(_($fdata['description']), $field,
+                                                  $fdata['type'], $fdata['required']);
+            }
+
+        }
+
+        return true;
+    }
+}
diff --git a/beatnik/lib/base.php b/beatnik/lib/base.php
new file mode 100644 (file)
index 0000000..99fc5bd
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Beatnik base inclusion file.
+ *
+ * $Horde: beatnik/lib/base.php,v 1.31 2009/09/15 09:20:03 duck Exp $
+ *
+ * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
+ *
+ * This file brings in all of the dependencies that every Beatnik
+ * script will need and sets up objects that all scripts use.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Beatnik
+ */
+
+// Check for a prior definition of HORDE_BASE (perhaps by an
+// auto_prepend_file definition for site customization).
+if (!defined('HORDE_BASE')) {
+    define('HORDE_BASE', dirname(__FILE__) . '/../..');
+}
+// Load the Horde Framework core, and set up inclusion paths.
+require_once HORDE_BASE . '/lib/core.php';
+
+// Registry.
+$registry = Horde_Registry::singleton();
+
+try {
+    $registry->pushApp('beatnik', array('check_perms' => (Horde_Util::nonInputVar('beatnik_authentication') != 'none')));
+} catch (Horde_Exception $e) {
+    if ($e->getCode() == Horde_Registry::PERMISSION_DENIED) {
+        Horde_Auth::authenticateFailure('beatnik', $e);
+    }
+    Horde::fatal($e, __FILE__, __LINE__, false);
+}
+
+$conf = &$GLOBALS['conf'];
+define('BEATNIK_TEMPLATES', $registry->get('templates'));
+
+// Find the base file path of Beatnik.
+if (!defined('BEATNIK_BASE')) {
+    define('BEATNIK_BASE', dirname(__FILE__) . '/..');
+}
+
+// Notification system.
+$notification = Horde_Notification::singleton();
+$notification->attach('status');
+
+// Beatnik base libraries.
+require_once BEATNIK_BASE . '/lib/Beatnik.php';
+require_once BEATNIK_BASE . '/lib/Driver.php';
+
+$GLOBALS['beatnik_driver'] = Beatnik_Driver::factory();
+if (is_a($GLOBALS['beatnik_driver'], 'PEAR_Error')) {
+    Horde::fatal($GLOBALS['beatnik_driver'], __FILE__, __LINE__);
+}
+
+// Get a list of domains to work with
+$domains = $GLOBALS['beatnik_driver']->getDomains();
+
+// Jump to new domain
+if (Horde_Util::getFormData('curdomain') !== null && !empty($domains)) {
+    $domain = $GLOBALS['beatnik_driver']->getDomain(Horde_Util::getFormData('curdomain'));
+    if (is_a($domain, 'PEAR_Error')) {
+        $notification->push($domain->getMessage() . ': ' . $domain->getDebugInfo(), 'horde.error');
+        $domain = $domains[0];
+    }
+
+    $_SESSION['beatnik']['curdomain'] = $domain;
+}
+
+// Determine if the user should see basic or advanced options
+if (!isset($_SESSION['beatnik']['expertmode'])) {
+    $_SESSION['beatnik']['expertmode'] = false;
+} elseif (Horde_Util::getFormData('expertmode') == 'toggle') {
+    if ($_SESSION['beatnik']['expertmode']) {
+        $notification->push(_('Expert Mode off'), 'horde.message');
+        $_SESSION['beatnik']['expertmode'] = false;
+    } else {
+        $notification->push(_('Expert Mode ON'), 'horde.warning');
+        $_SESSION['beatnik']['expertmode'] = true;
+    }
+}
+
+// Initialize the page marker
+if (!isset($_SESSION['beatnik']['curpage'])) {
+    $_SESSION['beatnik']['curpage'] = 0;
+}
+
+// Start output compression.
+if (!Horde_Util::nonInputVar('no_compress')) {
+    Horde::compressOutput();
+}
+
diff --git a/beatnik/lib/version.php b/beatnik/lib/version.php
new file mode 100644 (file)
index 0000000..914e747
--- /dev/null
@@ -0,0 +1 @@
+<?php define('BEATNIK_VERSION', 'H4 (1.0-cvs)') ?>
diff --git a/beatnik/listzones.php b/beatnik/listzones.php
new file mode 100644 (file)
index 0000000..7f55e15
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * $Horde: beatnik/listzones.php,v 1.25 2009/07/14 00:25:28 mrubinsk Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+require_once dirname(__FILE__) . '/lib/base.php';
+require_once 'Horde/Prefs/CategoryManager.php';
+
+// Unset the current domain since we are generating a zone list
+$_SESSION['beatnik']['curdomain'] = null;
+
+// Set up categories
+$cManager = new Prefs_CategoryManager();
+$categories = $cManager->get();
+$colors = $cManager->colors();
+$fgcolors = $cManager->fgColors();
+
+// Page results
+// Check for and store the current page in the session
+$page = Horde_Util::getGet('page', $_SESSION['beatnik']['curpage']);
+$_SESSION['beatnik']['curpage'] = $page;
+
+// Create the Pager UI
+$pager_vars = Horde_Variables::getDefaultVariables();
+$pager_vars->set('page', $page);
+$perpage = $prefs->getValue('domains_perpage');
+$pager = new Horde_UI_Pager('page', $pager_vars,
+                            array('num' => count($domains),
+                                  'url' => 'listzones.php',
+                                  'page_count' => 10,
+                                  'perpage' => $perpage));
+
+// Limit the domain list to the current page
+$domains = array_slice($domains, $page*$perpage, $perpage);
+
+$img_dir = $registry->getImageDir('horde');
+
+// Hide fields that the user does not want to see
+$fields = Beatnik::getRecFields('soa');
+foreach ($fields as $field_id => $field) {
+    if ($field['type'] == 'hidden' ||
+        ($field['infoset'] != 'basic' && !$_SESSION['beatnik']['expertmode'])) {
+        unset($fields[$field_id]);
+    }
+}
+
+// Add javascript navigation and striping
+Horde::addScriptFile('beatnik.js');
+Horde::addScriptFile('stripe.js', 'horde', true);
+
+// Initialization complete.  Render the page.
+require BEATNIK_TEMPLATES . '/common-header.inc';
+require BEATNIK_TEMPLATES . '/menu.inc';
+
+require BEATNIK_TEMPLATES . '/listzones/header.inc';
+foreach ($domains as $domain) {
+    $autourl = Horde_Util::addParameter(Horde::applicationUrl('autogenerate.php'), array('rectype' => 'soa', 'curdomain' => $domain['zonename']));
+    $deleteurl = Horde_Util::addParameter(Horde::applicationUrl('delete.php'), array('rectype' => 'soa', 'curdomain' => $domain['zonename']));
+    $viewurl = Horde_Util::addParameter(Horde::applicationUrl('viewzone.php'), 'curdomain', $domain['zonename']);
+    $editurl = Horde_Util::addParameter(Horde::applicationUrl('editrec.php'), array('curdomain' => $domain['zonename'], 'id' => $domain['id'], 'rectype' => 'soa'));
+    require BEATNIK_TEMPLATES . '/listzones/row.inc';
+}
+require BEATNIK_TEMPLATES . '/listzones/footer.inc';
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
diff --git a/beatnik/locale/en_US/help.xml b/beatnik/locale/en_US/help.xml
new file mode 100644 (file)
index 0000000..a0c7ac7
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version='1.0'?>
+<!-- $Horde: beatnik/locale/en_US/help.xml,v 1.2 2006/07/02 03:25:06 bklang Exp $ -->
+<help>
+
+<entry id="beatnik-overview">
+    <title>Beatnik Overview</title>
+
+    <heading>What is Beatnik?</heading>
+    <para>Beatnik manages DNS records in a convenient web interface.</para>
+</entry>
+
+<entry id="record-types">
+    <title>Record Types</title>
+</entry>
+
+<entry id="record-fields">
+    <title>Record Fields</title>
+
+    <heading>Timestamp</heading>
+    <para>This field is available only with the "ldap2dns" backend.</para>
+
+    <para>The Timestamp field provides tell the DNS server "Do Not Issue Before"
+        or "Do Not Issue After" a particular time.  If the "ttl" field is
+        nonzero or omitted, "timestamp" indicates a starting time for the record
+        to be valid.  If "ttl" is 0 the timestamp is an ending time, after which
+        the record will no longer be served.  <a href="http://cr.yp.to">tinydns
+        </a> will automatically adjust the ttl so that the record should not be
+        cached for more than a few seconds beyond the expiry.
+    </para>
+
+    <para>The format of the record is the number of seconds since midnight of
+        January 1st, 1970 expressed in TAI64, a 16 character hexadecimal string.
+        See
+        <a href="http://cr.yp.to/libtai/tai64.html#tai64">
+            http://cr.yp.to/libtai/tai64.html#tai64
+        </a> for more information on TAI64.
+    </para>
+</entry>
+
+</help>
diff --git a/beatnik/locale/sl_SI/LC_MESSAGES/beatnik.mo b/beatnik/locale/sl_SI/LC_MESSAGES/beatnik.mo
new file mode 100644 (file)
index 0000000..2a7b2b0
Binary files /dev/null and b/beatnik/locale/sl_SI/LC_MESSAGES/beatnik.mo differ
diff --git a/beatnik/po/beatnik.pot b/beatnik/po/beatnik.pot
new file mode 100644 (file)
index 0000000..79b9b7e
--- /dev/null
@@ -0,0 +1,549 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Horde Project
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: dev@lists.horde.org\n"
+"POT-Creation-Date: 2009-07-06 10:53+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: scripts/export_config.php:139
+msgid "-h, --help                   Show this help"
+msgstr ""
+
+#: scripts/export_config.php:141
+msgid "-p, --password[=password]    Horde login password"
+msgstr ""
+
+#: scripts/export_config.php:143
+msgid "-r, --rpc[=http://example.com/horde/rpc.php]    Remote url"
+msgstr ""
+
+#: scripts/export_config.php:142
+msgid "-t, --type[=type]    Export format"
+msgstr ""
+
+#: scripts/export_config.php:140
+msgid "-u, --username[=username]    Horde login username"
+msgstr ""
+
+#: lib/Beatnik.php:63
+msgid "A (Address)"
+msgstr ""
+
+#: templates/listzones/header.inc:45 templates/view/header.inc:43
+msgid "Actions"
+msgstr ""
+
+#: lib/Forms/EditRecord.php:31
+msgid "Add DNS Record"
+msgstr ""
+
+#: lib/Beatnik.php:32
+msgid "Add Record"
+msgstr ""
+
+#: lib/Beatnik.php:34
+msgid "Add Zone"
+msgstr ""
+
+#: lib/Driver/pdnsgsql.php:134 lib/Driver/pdnsgsql.php:191
+msgid ""
+"An error occurred while searching the database.  Details have been logged "
+"for the administrator."
+msgstr ""
+
+#: lib/Forms/DeleteRecord.php:19
+msgid "Are you sure you want to delete this record?"
+msgstr ""
+
+#: autogenerate.php:25 autogenerate.php:40 templates/listzones/row.inc:16
+#: lib/Forms/Autogenerate.php:22
+msgid "Autogenerate"
+msgstr ""
+
+#: autogenerate.php:33
+msgid "Autogeneration not performed"
+msgstr ""
+
+#: lib/Beatnik.php:65
+msgid "CNAME (Alias)"
+msgstr ""
+
+#: lib/Forms/DeleteRecord.php:37 lib/Forms/Autogenerate.php:22
+msgid "Cancel"
+msgstr ""
+
+#: lib/Forms/Autogenerate.php:21
+msgid "Choose a template for autogenerating the records:"
+msgstr ""
+
+#: lib/Beatnik.php:42
+msgid "Commit All"
+msgstr ""
+
+#: templates/view/header.inc:13
+msgid "Commit Changes"
+msgstr ""
+
+#: lib/Beatnik.php:133
+msgid "Contact e-mail address for this zone"
+msgstr ""
+
+#: lib/Driver/pdnsgsql.php:146
+msgid "Corrupt SOA found for zone.  Contact your administrator."
+msgstr ""
+
+#: scripts/export_config.php:44
+msgid "Couldn't read command-line options."
+msgstr ""
+
+#: lib/Beatnik.php:337
+msgid "DNS Service Record Port Number"
+msgstr ""
+
+#: lib/Beatnik.php:317
+msgid "DNS Service Record Priority"
+msgstr ""
+
+#: lib/Beatnik.php:327
+msgid "DNS Service Record Weight"
+msgstr ""
+
+#: config/prefs.php.dist:44
+msgid "Default Time-To-Live for new records."
+msgstr ""
+
+#: delete.php:24 delete.php:49 templates/listzones/row.inc:18
+#: templates/view/record.inc:18 templates/view/header.inc:25
+#: lib/Forms/DeleteRecord.php:37
+msgid "Delete"
+msgstr ""
+
+#: config/prefs.php.dist:10
+msgid "Display Preferences"
+msgstr ""
+
+#: templates/listzones/header.inc:15
+msgid "Domain Categories"
+msgstr ""
+
+#: lib/Beatnik.php:114 lib/Beatnik.php:275
+msgid "Domain Name"
+msgstr ""
+
+#: lib/api.php:46
+msgid "Domains"
+msgstr ""
+
+#: lib/Driver.php:326
+msgid "Driver not found."
+msgstr ""
+
+#: templates/listzones/row.inc:20 templates/view/record.inc:16
+#: templates/view/header.inc:23
+msgid "Edit"
+msgstr ""
+
+#: lib/Forms/EditRecord.php:31
+msgid "Edit DNS Record"
+msgstr ""
+
+#: lib/Beatnik.php:32
+msgid "Edit Record"
+msgstr ""
+
+#: templates/listzones/header.inc:20
+msgid "Edit categories and colors"
+msgstr ""
+
+#: templates/listzones/header.inc:19
+msgid "Edit domain groups and colors"
+msgstr ""
+
+#: config/autogenerate.php.dist:34
+msgid "Example Template"
+msgstr ""
+
+#: lib/Beatnik.php:38
+msgid "Expert Mode"
+msgstr ""
+
+#: lib/base.php:76
+msgid "Expert Mode ON"
+msgstr ""
+
+#: lib/base.php:73
+msgid "Expert Mode off"
+msgstr ""
+
+#: lib/Beatnik.php:169
+msgid "Expiration"
+msgstr ""
+
+#: scripts/export_config.php:117
+msgid "Have noting to do."
+msgstr ""
+
+#: lib/Beatnik.php:190 lib/Beatnik.php:211 lib/Beatnik.php:254
+#: lib/Beatnik.php:298 lib/Beatnik.php:349
+msgid "Hostname"
+msgstr ""
+
+#: lib/Beatnik.php:220 lib/Beatnik.php:232 lib/Beatnik.php:263
+#: lib/Beatnik.php:286 lib/Beatnik.php:307
+msgid "Hostname Target"
+msgstr ""
+
+#: lib/Beatnik.php:264
+msgid "Hostname for CNAME alias"
+msgstr ""
+
+#: lib/Beatnik.php:308
+msgid "Hostname for DNS Service Record"
+msgstr ""
+
+#: lib/Beatnik.php:221
+msgid "Hostname for Reverse DNS"
+msgstr ""
+
+#: lib/Beatnik.php:287
+msgid "Hostname of Authoritative Name Server"
+msgstr ""
+
+#: lib/Beatnik.php:233
+msgid "Hostname of Mail eXchanger"
+msgstr ""
+
+#: config/prefs.php.dist:36
+msgid "How many domain to display per page."
+msgstr ""
+
+#: lib/Beatnik.php:199
+msgid "IP Address"
+msgstr ""
+
+#: lib/Beatnik.php:212
+msgid "IP in Reverse notation (.in-addr.arpa)"
+msgstr ""
+
+#: lib/Beatnik.php:200
+msgid "IPv4 Network Address"
+msgstr ""
+
+#: lib/Driver/pdnsgsql.php:71 lib/Driver/pdnsgsql.php:121
+#: lib/Driver/pdnsgsql.php:174 lib/Driver/pdnsgsql.php:283
+#: lib/Driver/pdnsgsql.php:421
+msgid ""
+"Internal database error.  Details have been logged for the administrator."
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:246
+#, php-format
+msgid "Internal error: %s"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:349
+msgid "Invalid record type specified."
+msgstr ""
+
+#: lib/Beatnik.php:30
+msgid "List Domains"
+msgstr ""
+
+#: scripts/export_config.php:109
+#, php-format
+msgid "Logged in successfully as \"%s\"."
+msgstr ""
+
+#: scripts/export_config.php:105
+msgid "Login is incorrect."
+msgstr ""
+
+#: lib/Beatnik.php:66
+msgid "MX (Mail eXchange)"
+msgstr ""
+
+#: lib/Beatnik.php:242
+msgid "MX Preference (lower is more preferred)"
+msgstr ""
+
+#: scripts/export_config.php:137
+msgid ""
+"Mandatory arguments to long options are mandatory for short options too."
+msgstr ""
+
+#: lib/Beatnik.php:178
+msgid "Minimum"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:400
+#, php-format
+msgid "Missing required field %s to save record."
+msgstr ""
+
+#: lib/Beatnik.php:62
+msgid "NS (Name Server)"
+msgstr ""
+
+#: lib/Driver/pdnsgsql.php:424
+msgid "Not implemented."
+msgstr ""
+
+#: config/prefs.php.dist:9 config/prefs.php.dist:16
+msgid "Options"
+msgstr ""
+
+#: lib/Beatnik.php:64
+msgid "PTR (Reverse DNS)"
+msgstr ""
+
+#: lib/Beatnik.php:241
+msgid "Preference"
+msgstr ""
+
+#: lib/Beatnik.php:123
+msgid "Primary Nameserver"
+msgstr ""
+
+#: lib/Beatnik.php:124
+msgid "Primary nameserver for this zone"
+msgstr ""
+
+#: config/prefs.php.dist:17
+msgid "Record Defaults"
+msgstr ""
+
+#: lib/Beatnik.php:371
+msgid "Record Time-To-Live (seconds)"
+msgstr ""
+
+#: lib/Forms/EditRecord.php:39
+msgid "Record Type"
+msgstr ""
+
+#: lib/Beatnik.php:543
+#, php-format
+msgid "Record added: %s/%s"
+msgstr ""
+
+#: delete.php:30
+msgid "Record deleted"
+msgstr ""
+
+#: delete.php:38
+msgid "Record not deleted"
+msgstr ""
+
+#: lib/Beatnik.php:160
+msgid "Retry"
+msgstr ""
+
+#: lib/Beatnik.php:61 lib/Forms/EditRecord.php:36
+msgid "SOA (Start of Authority)"
+msgstr ""
+
+#: lib/Beatnik.php:67
+msgid "SRV (Service Record)"
+msgstr ""
+
+#: lib/Beatnik.php:336
+msgid "SRV Port"
+msgstr ""
+
+#: lib/Beatnik.php:316
+msgid "SRV Priority"
+msgstr ""
+
+#: lib/Beatnik.php:326
+msgid "SRV Weight"
+msgstr ""
+
+#: templates/menu.inc:17
+msgid "Select Domain"
+msgstr ""
+
+#: templates/menu.inc:11 templates/menu.inc:18
+msgid "Select _Domain"
+msgstr ""
+
+#: lib/Beatnik.php:141
+msgid "Serial"
+msgstr ""
+
+#: config/prefs.php.dist:11
+msgid "Set default display parameters."
+msgstr ""
+
+#: config/prefs.php.dist:18
+msgid "Set default record parameters."
+msgstr ""
+
+#: lib/Beatnik.php:191 lib/Beatnik.php:255 lib/Beatnik.php:299
+#: lib/Beatnik.php:350
+msgid "Short hostname for this record"
+msgstr ""
+
+#: lib/Beatnik.php:276
+msgid ""
+"Short sub-domain for NS record (leave blank unless creating a subdomain)"
+msgstr ""
+
+#: lib/Beatnik.php:536
+msgid "Skipping existing identical record"
+msgstr ""
+
+#: lib/Beatnik.php:359
+msgid "String payload for DNS TXT"
+msgstr ""
+
+#: lib/Beatnik.php:370
+msgid "TTL"
+msgstr ""
+
+#: lib/Beatnik.php:68
+msgid "TXT (Text Record)"
+msgstr ""
+
+#: lib/Forms/Autogenerate.php:29
+msgid "Template"
+msgstr ""
+
+#: lib/Driver/pdnsgsql.php:138
+msgid "Too many domains matched that name.  Contact your administrator."
+msgstr ""
+
+#: templates/view/header.inc:44 lib/Forms/DeleteRecord.php:27
+msgid "Type"
+msgstr ""
+
+#: lib/Beatnik.php:102
+msgid "UID"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:469
+#, php-format
+msgid "Unable to add record to LDAP. Reason: %s"
+msgstr ""
+
+#: commit.php:49
+#, php-format
+msgid "Unable to construct valid SOA for %s.  Not incrementing serial."
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:321
+#, php-format
+msgid "Unable to delete record.  Reason: %s"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:300
+msgid "Unable to delete record: No record ID specified."
+msgstr ""
+
+#: lib/Beatnik.php:430
+msgid "Unable to determine if domain needs committing: invalid parameter."
+msgstr ""
+
+#: lib/Driver.php:147
+msgid "Unable to locate requested record."
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:452
+#, php-format
+msgid "Unable to modify record.  Reason: %s"
+msgstr ""
+
+#: lib/Driver.php:108
+#, php-format
+msgid "Unable to read requested domain %s"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:442
+#, php-format
+msgid "Unable to rename old object.  Reason: %s"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:146
+#, php-format
+msgid "Unable to retrieve data from LDAP results: %s"
+msgstr ""
+
+#: lib/Beatnik.php:103
+msgid "Unique Identifier (Used as Record ID)"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:437
+msgid "Unsupported operation: cannot rename a domain."
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:309
+msgid "Unsupported recursive delete."
+msgstr ""
+
+#: scripts/export_config.php:135
+#, php-format
+msgid "Usage: %s [OPTIONS]..."
+msgstr ""
+
+#: lib/Driver.php:79
+msgid "You are not permitted to view any domains."
+msgstr ""
+
+#: lib/Driver.php:207
+msgid "You do not have permission to create new domains."
+msgstr ""
+
+#: lib/Driver.php:220
+msgid "You do not have permssion to edit the SOA of this zone."
+msgstr ""
+
+#: lib/Driver.php:225
+msgid "You do not have permssion to edit this record."
+msgstr ""
+
+#: templates/common-header.inc:15
+#, php-format
+msgid "You have uncommitted changes in %s."
+msgstr ""
+
+#: lib/Beatnik.php:132
+msgid "Zone Contact"
+msgstr ""
+
+#: lib/Beatnik.php:115
+msgid "Zone Domain Name"
+msgstr ""
+
+#: lib/Beatnik.php:170
+msgid "Zone Expiry"
+msgstr ""
+
+#: lib/Beatnik.php:179
+msgid "Zone Minimum"
+msgstr ""
+
+#: lib/Beatnik.php:152
+msgid "Zone Refresh"
+msgstr ""
+
+#: lib/Beatnik.php:161
+msgid "Zone Retry"
+msgstr ""
+
+#: lib/Beatnik.php:142
+msgid "Zone Serial Number"
+msgstr ""
+
+#: commit.php:46
+#, php-format
+msgid "Zone serial for %s incremented."
+msgstr ""
diff --git a/beatnik/po/sl_SI.po b/beatnik/po/sl_SI.po
new file mode 100644 (file)
index 0000000..1357c6d
--- /dev/null
@@ -0,0 +1,503 @@
+# Slovenian translations for Beatnik packaga
+# Slovenski prevod Beatnik paketa
+# Copyright (C) 2006 Horde Project
+# This file is distributed under the same license as the horde package.
+# Automatically generated, 2006.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: dev@lists.horde.org\n"
+"POT-Creation-Date: 2006-09-13 12:27+0200\n"
+"PO-Revision-Date: 2006-04-30 10:32+0100\n"
+"Last-Translator: duck@obala.net\n"
+"Language-Team: sl_SI <duck@obala.net>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: scripts/commands.php:133 scripts/tinydns.php:167 scripts/apache.php:142
+#: scripts/unixhosts.php:123
+msgid "-h, --help                   Show this help"
+msgstr ""
+
+#: scripts/commands.php:135 scripts/tinydns.php:169 scripts/apache.php:144
+#: scripts/unixhosts.php:125
+msgid "-p, --password[=password]    Horde login password"
+msgstr ""
+
+#: scripts/commands.php:134 scripts/tinydns.php:168 scripts/apache.php:143
+#: scripts/unixhosts.php:124
+msgid "-u, --username[=username]    Horde login username"
+msgstr ""
+
+#: lib/Beatnik.php:61
+msgid "A (Address)"
+msgstr "A (naslov)"
+
+#: templates/listzones/header.inc:44 templates/view/header.inc:45
+msgid "Actions"
+msgstr "Ukazi"
+
+#: lib/Forms/EditRecord.php:22
+msgid "Add DNS Record"
+msgstr "Dodaj DNS zapis"
+
+#: lib/Beatnik.php:33
+msgid "Add Record"
+msgstr "Dodaj zapis"
+
+#: lib/Forms/DeleteRecord.php:22
+msgid "Are you sure you want to delete this record?"
+msgstr "Resnično želite izbrisati ta zapis?"
+
+#: autogenerate.php:28 autogenerate.php:43 templates/listzones/row.inc:16
+#: lib/Forms/Autogenerate.php:28
+msgid "Autogenerate"
+msgstr "Samodejno tvorjenje"
+
+#: autogenerate.php:36
+msgid "Autogeneration not performed"
+msgstr "Samodejno tvorjenje ni bilo izvršeno"
+
+#: lib/Beatnik.php:63
+msgid "CNAME (Alias)"
+msgstr ""
+
+#: lib/Forms/DeleteRecord.php:40 lib/Forms/Autogenerate.php:28
+msgid "Cancel"
+msgstr "Prekliči"
+
+#: lib/Forms/Autogenerate.php:27
+msgid "Choose a template for autogenerating the records:"
+msgstr "Izberite šablono za samodejno tvorjenje zapisov"
+
+#: lib/Beatnik.php:40
+msgid "Commit All"
+msgstr "Izvrši vse"
+
+#: templates/view/header.inc:13
+msgid "Commit Changes"
+msgstr "Izvrši spremembe"
+
+#: lib/Beatnik.php:131
+msgid "Contact e-mail address for this zone"
+msgstr "Kontaktni email za to zono"
+
+#: scripts/commands.php:47 scripts/tinydns.php:50 scripts/apache.php:47
+#: scripts/unixhosts.php:47
+msgid "Couldn't read command-line options."
+msgstr ""
+
+#: lib/Beatnik.php:335
+msgid "DNS Service Record Port Number"
+msgstr ""
+
+#: lib/Beatnik.php:315
+msgid "DNS Service Record Priority"
+msgstr ""
+
+#: lib/Beatnik.php:325
+msgid "DNS Service Record Weight"
+msgstr ""
+
+#: delete.php:29 delete.php:54 templates/listzones/row.inc:18
+#: templates/view/record.inc:18 templates/view/header.inc:25
+#: lib/Forms/DeleteRecord.php:40
+msgid "Delete"
+msgstr "Izbriši"
+
+#: config/prefs.php.dist:10
+msgid "Display details"
+msgstr "Podrobnosti prikazovanja"
+
+#: config/prefs.php.dist:9
+msgid "Display listings"
+msgstr "Nastavitve izpisovanja"
+
+#: templates/listzones/header.inc:15
+msgid "Domain Categories"
+msgstr "Kategorije domen"
+
+#: lib/Beatnik.php:112 lib/Beatnik.php:273
+msgid "Domain Name"
+msgstr "Ime domene"
+
+#: lib/api.php:45
+msgid "Domains"
+msgstr "Domene"
+
+#: templates/listzones/row.inc:20 templates/view/record.inc:16
+#: templates/view/header.inc:23
+msgid "Edit"
+msgstr "Uredi"
+
+#: lib/Forms/EditRecord.php:22
+msgid "Edit DNS Record"
+msgstr "Uredi DNS zapis"
+
+#: lib/Beatnik.php:33
+msgid "Edit Record"
+msgstr "Uredi zapis"
+
+#: templates/listzones/header.inc:20
+msgid "Edit categories and colors"
+msgstr "Uredi kategorije in barve"
+
+#: templates/listzones/header.inc:19
+msgid "Edit domain groups and colors"
+msgstr "Uredi grupe in barve"
+
+#: config/autogenerate.php.dist:34
+msgid "Example Template"
+msgstr "Primer šablone"
+
+#: lib/Beatnik.php:36
+msgid "Expert Mode"
+msgstr "Napredni način"
+
+#: lib/base.php:79
+msgid "Expert Mode ON"
+msgstr "Napredni način je bil vklopljen"
+
+#: lib/base.php:76
+msgid "Expert Mode off"
+msgstr "Napredni način ke bil izklopljen"
+
+#: lib/Beatnik.php:167
+msgid "Expiration"
+msgstr "Poteče"
+
+#: lib/Beatnik.php:188 lib/Beatnik.php:209 lib/Beatnik.php:252
+#: lib/Beatnik.php:296 lib/Beatnik.php:347
+msgid "Hostname"
+msgstr ""
+
+#: lib/Beatnik.php:218 lib/Beatnik.php:230 lib/Beatnik.php:261
+#: lib/Beatnik.php:284 lib/Beatnik.php:305
+msgid "Hostname Target"
+msgstr ""
+
+#: lib/Beatnik.php:262
+msgid "Hostname for CNAME alias"
+msgstr ""
+
+#: lib/Beatnik.php:306
+msgid "Hostname for DNS Service Record"
+msgstr ""
+
+#: lib/Beatnik.php:219
+msgid "Hostname for Reverse DNS"
+msgstr ""
+
+#: lib/Beatnik.php:285
+msgid "Hostname of Authoritative Name Server"
+msgstr ""
+
+#: lib/Beatnik.php:231
+msgid "Hostname of Mail eXchanger"
+msgstr ""
+
+#: config/prefs.php.dist:28
+msgid "How many domain to display per page."
+msgstr "Koliko domen naj prikažem na stran?"
+
+#: lib/Beatnik.php:197
+msgid "IP Address"
+msgstr "IP naslov"
+
+#: lib/Beatnik.php:210
+msgid "IP in Reverse notation (.in-addr.arpa)"
+msgstr ""
+
+#: lib/Beatnik.php:198
+msgid "IPv4 Network Address"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:267
+#, php-format
+msgid "Internal error: %s"
+msgstr "Interna napaka: %s"
+
+#: lib/Driver/ldap2dns.php:370
+msgid "Invalid record type specified."
+msgstr "Podan je bil neveljavni način zapisa."
+
+#: lib/Beatnik.php:32
+msgid "List Domains"
+msgstr "Spisek domen"
+
+#: scripts/commands.php:87 scripts/tinydns.php:90 scripts/apache.php:87
+#: scripts/unixhosts.php:87
+#, php-format
+msgid "Logged in successfully as \"%s\"."
+msgstr ""
+
+#: scripts/commands.php:83 scripts/tinydns.php:86 scripts/apache.php:83
+#: scripts/unixhosts.php:83
+msgid "Login is incorrect."
+msgstr ""
+
+#: lib/Beatnik.php:64
+msgid "MX (Mail eXchange)"
+msgstr ""
+
+#: lib/Beatnik.php:240
+msgid "MX Preference (lower is more preferred)"
+msgstr ""
+
+#: scripts/commands.php:131 scripts/tinydns.php:165 scripts/apache.php:140
+#: scripts/unixhosts.php:121
+msgid ""
+"Mandatory arguments to long options are mandatory for short options too."
+msgstr ""
+
+#: lib/Beatnik.php:176
+msgid "Minimum"
+msgstr "Najmanj"
+
+#: lib/Driver/ldap2dns.php:425
+#, php-format
+msgid "Missing required field %s to save record."
+msgstr "Ne morem shraniti zapisa saj manjka zahtevano polje %s."
+
+#: lib/Beatnik.php:60
+msgid "NS (Name Server)"
+msgstr ""
+
+#: lib/Beatnik.php:62
+msgid "PTR (Reverse DNS)"
+msgstr ""
+
+#: lib/Beatnik.php:239
+msgid "Preference"
+msgstr ""
+
+#: lib/Beatnik.php:121
+msgid "Primary Nameserver"
+msgstr ""
+
+#: lib/Beatnik.php:122
+msgid "Primary nameserver for this zone"
+msgstr ""
+
+#: lib/Beatnik.php:369
+msgid "Record Time-To-Live (seconds)"
+msgstr ""
+
+#: lib/Forms/EditRecord.php:28
+msgid "Record Type"
+msgstr "Tip zapisa"
+
+#: lib/Beatnik.php:532
+msgid "Record added"
+msgstr "Zapis je bil dodan"
+
+#: delete.php:35
+msgid "Record deleted"
+msgstr "Zapis je bil izbrisan"
+
+#: delete.php:43
+msgid "Record not deleted"
+msgstr "Zapis ni bil izbrisan"
+
+#: lib/Beatnik.php:158
+msgid "Retry"
+msgstr "Poskusi znova"
+
+#: lib/Beatnik.php:59
+msgid "SOA (Start of Authority)"
+msgstr ""
+
+#: lib/Beatnik.php:65
+msgid "SRV (Service Record)"
+msgstr ""
+
+#: lib/Beatnik.php:334
+msgid "SRV Port"
+msgstr ""
+
+#: lib/Beatnik.php:314
+msgid "SRV Priority"
+msgstr ""
+
+#: lib/Beatnik.php:324
+msgid "SRV Weight"
+msgstr ""
+
+#: templates/menu.inc:17
+msgid "Select Domain"
+msgstr "Izberi domeno"
+
+#: templates/menu.inc:11 templates/menu.inc:18
+msgid "Select _Domain"
+msgstr "Izberi Domeno"
+
+#: lib/Beatnik.php:139
+msgid "Serial"
+msgstr "Serijska št."
+
+#: config/prefs.php.dist:11
+msgid "Set default display parameters."
+msgstr "Nastavite prenstavljene parameterje prikaza."
+
+#: lib/Beatnik.php:189 lib/Beatnik.php:253 lib/Beatnik.php:297
+#: lib/Beatnik.php:348
+msgid "Short hostname for this record"
+msgstr ""
+
+#: lib/Beatnik.php:274
+msgid "Short sub-domain for NS record"
+msgstr ""
+
+#: lib/Beatnik.php:525
+msgid "Skipping existing identical record"
+msgstr "Preskoži že obstoječe zapise"
+
+#: lib/Beatnik.php:357
+msgid "String payload for DNS TXT"
+msgstr ""
+
+#: lib/Beatnik.php:368
+msgid "TTL"
+msgstr ""
+
+#: lib/Beatnik.php:66
+msgid "TXT (Text Record)"
+msgstr ""
+
+#: lib/Forms/Autogenerate.php:35
+msgid "Template"
+msgstr "Šablona"
+
+#: templates/view/header.inc:46 lib/Forms/DeleteRecord.php:30
+msgid "Type"
+msgstr "Tip"
+
+#: lib/Beatnik.php:100
+msgid "UUID"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:488
+#, php-format
+msgid "Unable to add record to LDAP. Reason: %s"
+msgstr "Ne morem dodati zapisa v LDAP: %s"
+
+#: commit.php:51
+#, php-format
+msgid "Unable to construct valid SOA for %s.  Not incrementing serial."
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:342
+#, php-format
+msgid "Unable to delete record.  Reason: %s"
+msgstr "Nemorem dodati zapisa: %s"
+
+#: lib/Driver/ldap2dns.php:321
+msgid "Unable to delete record: No record ID specified."
+msgstr "Ne morem izbrisati zapisa. Niste podatli IDja."
+
+#: lib/Beatnik.php:427
+msgid "Unable to determine if domain needs committing: invalid parameter."
+msgstr ""
+
+#: lib/Driver.php:100
+msgid "Unable to locate requested record."
+msgstr "Ne morem najti izbranega zapisa"
+
+#: lib/Driver/ldap2dns.php:479
+#, php-format
+msgid "Unable to modify record.  Reason: %s"
+msgstr "Ne morem ažurirati zapisa:  %s"
+
+#: lib/Driver/ldap2dns.php:198
+#, php-format
+msgid "Unable to read requested domain %s"
+msgstr "Ne morem prebrati zahtevane domene %s"
+
+#: lib/Driver/ldap2dns.php:469
+#, php-format
+msgid "Unable to rename old object.  Reason: %s"
+msgstr "Ne preimenovati starega objekta: %s"
+
+#: lib/Driver/ldap2dns.php:148
+#, php-format
+msgid "Unable to retrieve data from LDAP results: %s"
+msgstr "Ne morem prebrati podatkov: %s"
+
+#: lib/Beatnik.php:101
+msgid "Universally Unique Identifier (Used as Record ID)"
+msgstr ""
+
+#: lib/Driver/ldap2dns.php:464
+msgid "Unsupported operation: cannot rename a domain."
+msgstr "Nepodprti ukaz: ne morem preimenovati domene."
+
+#: lib/Driver/ldap2dns.php:330
+msgid "Unsupported recursive delete."
+msgstr "Nepodarti rekurzivni izbris."
+
+#: scripts/commands.php:129 scripts/tinydns.php:163 scripts/apache.php:138
+#: scripts/unixhosts.php:119
+#, php-format
+msgid "Usage: %s [OPTIONS]..."
+msgstr ""
+
+#: lib/Driver.php:57
+msgid "You are not permitted to view any domains."
+msgstr "Nimate pravic za pregled katerekoli domene."
+
+#: lib/Driver.php:164
+msgid "You do not have permission to create new domains."
+msgstr "Nimate pravic za tvorjenje novih domenskih zapisov."
+
+#: lib/Driver.php:177
+msgid "You do not have permssion to edit the SOA of this zone."
+msgstr "Nimate pravic za urejanje novih domenskih zapisov."
+
+#: lib/Driver.php:182
+msgid "You do not have permssion to edit this record."
+msgstr "Nimate pravic za tvorjenje urejanje tega zapisa."
+
+#: templates/common-header.inc:15
+#, php-format
+msgid "You have uncommitted changes in %s."
+msgstr "Preklicali ste spremembe za %s."
+
+#: lib/Beatnik.php:130
+msgid "Zone Contact"
+msgstr "Kontakt"
+
+#: lib/Beatnik.php:113
+msgid "Zone Domain Name"
+msgstr "Ime domene"
+
+#: lib/Beatnik.php:168
+msgid "Zone Expiry"
+msgstr "Poteče"
+
+#: lib/Beatnik.php:177
+msgid "Zone Minimum"
+msgstr ""
+
+#: lib/Beatnik.php:150
+msgid "Zone Refresh"
+msgstr "Osvežitev"
+
+#: lib/Beatnik.php:159
+msgid "Zone Retry"
+msgstr ""
+
+#: lib/Beatnik.php:140
+msgid "Zone Serial Number"
+msgstr "Serijska številka"
+
+#: commit.php:48
+#, php-format
+msgid "Zone serial for %s incremented."
+msgstr ""
+
+#: lib/Forms/EditRecord.php:39 lib/Forms/EditRecord.php:41
+#: lib/Forms/DeleteRecord.php:35
+msgid "description"
+msgstr "opis"
diff --git a/beatnik/scripts/export_config.php b/beatnik/scripts/export_config.php
new file mode 100644 (file)
index 0000000..4b7e3fd
--- /dev/null
@@ -0,0 +1,331 @@
+<?php
+/**
+ * Helper for creating mass config files
+ *
+ * NOTE You should change the creation section to swit your needs
+ *
+ * USE:
+ *  php ./export_config.php --type=TYPE --username=USR --password=PASS --url=URL > hosts
+ *
+ * $Horde: beatnik/scripts/export_config.php,v 1.2 2009/07/15 15:05:33 duck Exp $
+ *
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * 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 Duck <duck@obala.net>
+ * @package Beatnik
+ */
+
+define('AUTH_HANDLER', true);
+define('HORDE_BASE', dirname(__FILE__) . '/../../');
+define('BEATNIK_BASE', HORDE_BASE . '/beatnik');
+
+// Do CLI checks and environment setup first.
+require_once HORDE_BASE . '/lib/core.php';
+require_once 'Horde/CLI.php';
+
+// Make sure no one runs this from the web.
+if (!Horde_CLI::runningFromCLI()) {
+    exit("Must be run from the command line\n");
+}
+
+// Load the CLI environment.
+Horde_CLI::init();
+$cli = &Horde_CLI::singleton();
+
+// We accept the user name on the command-line.
+require_once 'Console/Getopt.php';
+$ret = Console_Getopt::getopt(Console_Getopt::readPHPArgv(), 'h:u:p:t:r',
+                              array('help', 'username=', 'password=', 'type=', 'rpc='));
+
+if (is_a($ret, 'PEAR_Error')) {
+    $error = _("Couldn't read command-line options.");
+    Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+    $cli->fatal($error);
+}
+
+// Show help and exit if no arguments were set.
+list($opts, $args) = $ret;
+if (!$opts) {
+    showHelp();
+    exit;
+}
+
+foreach ($opts as $opt) {
+    list($optName, $optValue) = $opt;
+    switch ($optName) {
+    case 'u':
+    case '--username':
+        $username = $optValue;
+        break;
+
+    case 'p':
+    case '--password':
+        $password = $optValue;
+        break;
+        
+    case 't':
+    case '--type':
+        $type = $optValue;
+        break;
+        
+    case 'r':
+    case '--rpc':
+        $rpc = $optValue;
+        break;
+
+    case 'h':
+    case '--help':
+        showHelp();
+        exit;
+    }
+}
+
+// We will fetch data from RPC
+if (!empty($rpc)) {
+
+    require_once 'Horde/RPC.php';
+    $domains = Horde_RPC::request('xmlrpc', $rpc,
+                                                            'dns.getDomains',
+                                                                array(),
+                                                                    array('user' => $username,
+                                                                                'pass' => $password));
+    if (is_a($result, 'PEAR_Error')) {
+        $cli->fatal($domains);
+    }
+    
+// Login to horde if username & password are set and load module.
+} elseif (!empty($username) && !empty($password)) {
+
+     require_once HORDE_BASE . '/lib/base.php';
+    $auth = &Horde_Auth::singleton($conf['auth']['driver']);
+    if (!$auth->authenticate($username, array('password' => $password))) {
+        $error = _("Login is incorrect.");
+        Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR);
+        $cli->fatal($error);
+    } else {
+        $msg = sprintf(_("Logged in successfully as \"%s\"."), $username);
+        Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+        $cli->message($msg, 'cli.success');
+    }
+
+    require_once BEATNIK_BASE . '/lib/base.php';
+              
+} else {
+    $msg = _("Have noting to do.");
+    $cli->fatal($msg);
+}
+
+// Steps
+if (empty($type)) {
+    $type = 'tinydns';
+}
+$function = '_' . $type;
+echo $function();
+
+/**
+ * Show the command line arguments that the script accepts.
+ */
+function showHelp()
+{
+    global $cli;
+
+    $cli->writeln(sprintf(_("Usage: %s [OPTIONS]..."), basename(__FILE__)));
+    $cli->writeln();
+    $cli->writeln(_("Mandatory arguments to long options are mandatory for short options too."));
+    $cli->writeln();
+    $cli->writeln(_("-h, --help                   Show this help"));
+    $cli->writeln(_("-u, --username[=username]    Horde login username"));
+    $cli->writeln(_("-p, --password[=password]    Horde login password"));
+    $cli->writeln(_("-t, --type[=type]    Export format"));
+    $cli->writeln(_("-r, --rpc[=http://example.com/horde/rpc.php]    Remote url"));
+    $cli->writeln();
+}
+
+/**
+ * Get domain records
+ */
+function _getRecords($domain)
+{
+    if (empty($GLOBALS['rpc'])) {
+        $result = $GLOBALS['beatnik_driver']->getRecords($domain);
+    } else {
+        $result = Horde_RPC::request('xmlrpc', $GLOBALS['rpc'],
+                                                            'dns.getRecords',
+                                                                array($domain),
+                                                                    array('user' => $GLOBALS['username'],
+                                                                                'pass' => $GLOBALS['password']));
+    }
+    
+    if (is_a($result, 'PEAR_Error')) {
+        $GLOBALS['cli']->fatal($result);    
+    } else {
+        return $result;
+    }
+}
+
+/**
+ * Generate unix hosts file
+ */
+function _hosts()
+{
+    $c = "# Generated with beatnik on " . date('Y-m-d h:i:s') . " by " . $GLOBALS['username'] . "\n\n";
+    foreach ($GLOBALS['domains'] as $domain) {
+
+        $zonename = $domain['zonename'];
+        $tld = substr($zonename, strrpos($zonename, '.')+1);
+        $domain = substr($zonename, 0, strrpos($zonename, '.'));
+
+        foreach ($zonedata['cname'] as $id => $values) {
+            extract($values);
+            if (!empty($hostname)) {
+                $hostname .= '.';
+            }
+            $c .= "$pointer $hostname.$domain.$tld\n";
+        }
+    }
+    
+    return $c;
+}
+
+/**
+ * Generate bash script
+ */
+function _bash()
+{
+    $c = "# Generated with beatnik on " . date('Y-m-d h:i:s') . " by " . $GLOBALS['username'] . "\n\n";
+    foreach ($GLOBALS['domains'] as $domain) {
+
+        $zonename = $domain['zonename'];
+        $tld = substr($zonename, strrpos($zonename, '.')+1);
+        $domain = substr($zonename, 0, strrpos($zonename, '.'));
+
+        $c .= "useradd $zonename" . "_$tld -d /var/www/$zonename" . "_$tld -s /bin/false" . "\n";
+        $c .= "mkdir /var/www/$zonename" . "_$tld/" . "\n";
+        $c .= "chown -R $zonename" . "_$tld:apache /var/www/$zonename" . "_$tld/" . "\n";
+
+        foreach ($zonedata['cname'] as $id => $values) {
+            extract($values);
+            if (empty($hostname)) {
+                // use empty hostname as alias for www
+                $c .= "ln -s /var/www/$zonename" . "_$tld/$zonename.$tld " .
+                           "/var/www/$zonename" . "_$tld/$hostname.$zonename.$tld" . "\n";
+                continue; 
+            }
+
+            $c .= "mkdir /var/www/$zonename" . "_$tld/$hostname.$zonename.$tld" . "\n";
+            $c .= "mkdir /var/tmp/www/$hostname.$zonename.$tld" . "\n";
+            $c .= "mkdir /var/log/apache2/$hostname.$zonename.$tld" . "\n";
+        }
+    }
+    
+    return $c;
+}
+
+/**
+ * Generate apache host defs
+ */
+function _apache()
+{
+    $c = "# Generated with beatnik on " . date('Y-m-d h:i:s') . " by " . $GLOBALS['username'] . "\n\n";
+    foreach ($GLOBALS['domains'] as $domain) {
+        // Get default data and skip if no cnames
+        $zonedata = _getRecords($domain['zonename']);
+        if (!isset($zonedata['cname'])) {
+            continue;
+        }
+
+        // data
+        $zonename = $domain['zonename'];
+        $tld = substr($zonename, strrpos($zonename, '.')+1);
+        $domain = substr($zonename, 0, strrpos($zonename, '.'));
+        foreach ($zonedata['cname'] as $id => $values) {
+
+            extract($values);
+            if (empty($hostname)) {
+                continue; // use empty hostname as alias for www
+            }
+
+            $c .= "\n";
+            $c .= "<VirtualHost $hostname.$zonename:80>\n";
+            $c .= "    DocumentRoot /var/www/$domain" . "_$tld/$hostname.$zonename\n";
+            $c .= "    ServerName $hostname.$zonename\n";
+            if ($hostname == 'www') {
+                $c .= "    ServerAlias $zonename\n";
+            }
+            $c .= "    ErrorLog logs/$hostname.$zonename/error_log\n";
+            $c .= "    TransferLog logs/$hostname.$zonename/access_log\n";
+            $c .= "    php_admin_value upload_tmp_dir \"/var/tmp/www/$hostname.$zonename\"\n";
+            $c .= "    php_admin_value open_basedir \".:/usr/lib/php:/var/tmp/www/$hostname.$zonename:/var/www/$domain" . "_$tld/$hostname.$zonename\"\n";
+            $c .= "</VirtualHost>\n";
+        }
+    }
+    
+    return $c;
+}
+
+/**
+ * Generate tinydns defs
+ */
+function _tinydns()
+{
+    $c = "# Generated with beatnik on " . date('Y-m-d h:i:s') . " by " . $GLOBALS['username'] . "\n\n";
+    foreach ($GLOBALS['domains'] as $domain) {
+        // Get zone data
+        $zonedata = _getRecords($domain['zonename']);
+        $c .= '# ' . $domain['zonename'] . "\n";
+
+        // default SOA, NS
+        $c .= '.' . $domain['zonename'] . ':' . gethostbyname($domain['zonens']) . ':' . $domain['zonens'] . ':' . $domain['ttl'] . "\n";
+
+        // NS records
+        if (isset($zonedata['ns'])) {
+            foreach ($zonedata['ns'] as $id => $values) {
+                $c .= '.' . $domain['zonename'] . ':'.  gethostbyname($values['pointer']) . ':' . $values['pointer'] . ':' . $values['ttl'] . "\n";
+            }
+        }
+
+        // MX records
+        if (isset($zonedata['mx'])) {
+            foreach ($zonedata['mx'] as $id => $values) {
+                $c .= '@' . $domain['zonename'] . ':'.  gethostbyname($values['pointer']) . ':' . $values['pointer'] . ':' . $values['pref'] . ':' . $values['ttl'] . "\n";
+            }
+        }
+
+        // PTR records
+        if (isset($zonedata['ptr'])) {
+            foreach ($zonedata['ptr'] as $id => $values) {
+                $c .= '=';
+                $c .= $values['hostname'] . '.' . $domain['zonename'] . ':' . $values['pointer'] . ':' . $values['ttl'] . "\n";
+            }
+        }
+
+        // A records
+        if (isset($zonedata['a'])) {
+            foreach ($zonedata['a'] as $id => $values) {
+                $c .= '+';
+                if ($values['hostname']) {
+                    $c .= $values['hostname'] . '.';
+                }
+                $c .= $domain['zonename'] . ':' . $values['ipaddr'] . ':' . $values['ttl'] . "\n";
+            }
+        }
+
+        // CNAME records
+        if (isset($zonedata['cname'])) {
+            foreach ($zonedata['cname'] as $id => $values) {
+                $c .= 'C';
+                if ($values['hostname']) {
+                    $c .= $values['hostname'] . '.';
+                }
+                $c .= $domain['zonename'] . ':' . $values['pointer'] . ':' . $values['ttl'] . "\n";
+            }
+        }
+
+        $c .= "\n";
+    }
+        
+    return $c;
+}
diff --git a/beatnik/scripts/sql/beatnik.mysql.php b/beatnik/scripts/sql/beatnik.mysql.php
new file mode 100644 (file)
index 0000000..3c2f9d8
--- /dev/null
@@ -0,0 +1,132 @@
+--
+-- $Horde: beatnik/scripts/sql/beatnik.mysql.php,v 1.5 2006/08/13 18:52:47 duck Exp $
+-- 
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_a`
+-- 
+
+CREATE TABLE `beatnik_a` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `hostname` varchar(255) NOT NULL,
+  `ipaddr` varchar(255) NOT NULL,
+  `ttl` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_cname`
+-- 
+
+CREATE TABLE `beatnik_cname` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `hostname` varchar(255) NOT NULL,
+  `pointer` varchar(255) NOT NULL,
+  `ttl` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_mx`
+-- 
+
+CREATE TABLE `beatnik_mx` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `pointer` varchar(255) NOT NULL,
+  `pref` varchar(255) NOT NULL,
+  `ttl` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_ns`
+-- 
+
+CREATE TABLE `beatnik_ns` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `hostname` varchar(255) default NULL,
+  `pointer` varchar(255) default '',
+  `ttl` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_ptr`
+-- 
+
+CREATE TABLE `beatnik_ptr` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `hostname` varchar(255) NOT NULL,
+  `pointer` varchar(255) NOT NULL,
+  `ttl` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_soa`
+-- 
+
+CREATE TABLE `beatnik_soa` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `zonens` varchar(255) NOT NULL,
+  `zonecontact` varchar(255) default NULL,
+  `serial` varchar(255) default NULL,
+  `refresh` int(10) unsigned default NULL,
+  `retry` int(10) unsigned default NULL,
+  `expire` varchar(255) default NULL,
+  `minimum` varchar(255) default NULL,
+  `ttl` int(11) NOT NULL default '3600',
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `zonename` (`zonename`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 PACK_KEYS=1;
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_srv`
+-- 
+
+CREATE TABLE `beatnik_srv` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `hostname` varchar(255) NOT NULL,
+  `pointer` varchar(255) NOT NULL,
+  `priority` varchar(255) NOT NULL,
+  `weight` varchar(255) NOT NULL,
+  `port` varchar(255) NOT NULL,
+  `ttl` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `beatnik_txt`
+-- 
+
+CREATE TABLE `beatnik_txt` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `zonename` varchar(255) NOT NULL,
+  `hostname` varchar(255) NOT NULL,
+  `text` varchar(255) NOT NULL,
+  `ttl` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
diff --git a/beatnik/templates/common-header.inc b/beatnik/templates/common-header.inc
new file mode 100644 (file)
index 0000000..2c22532
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * $Horde: beatnik/templates/common-header.inc,v 1.9 2009/09/15 09:20:03 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+// This check has to come after the page has finished all work in case the
+// status has changed due to a now-completed edit.
+if (count(Beatnik::needCommit())) {
+    foreach(Beatnik::needCommit() as $domain) {
+        $notification->push(sprintf(_("You have uncommitted changes in %s."), $domain));
+    }
+}
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
+<!--    Beatnik Copyright 2005-2007, Ben Klang <ben@alkaloid.net> -->
+<!--    Horde Project: http://www.horde.org/                       -->
+<!--    Beatnik: http://projects.alkaloid.net/                     -->
+<!--    Horde Licenses: http://www.horde.org/licenses/             -->
+<?php echo !empty($language) ? '<html lang="' . strtr($language, '_', '-') . '">' : '<html>' ?>
+<head>
+<?php
+
+$page_title = $registry->get('name');
+if (!empty($title)) $page_title .= ' :: ' . $title;
+if (!empty($refresh_time) && ($refresh_time > 0) && !empty($refresh_url)) {
+    echo "<meta http-equiv=\"refresh\" content=\"$refresh_time;url=$refresh_url\">\n";
+}
+
+Horde::includeScriptFiles();
+
+?>
+<title><?php echo htmlspecialchars($page_title) ?></title>
+<link href="<?php echo $GLOBALS['registry']->getImageDir()?>/favicon.ico" rel="SHORTCUT ICON" />
+<?php echo Horde::includeStylesheetFiles(); ?>
+</head>
+<body<?php if ($bc = Horde_Util::nonInputVar('bodyClass')) echo ' class="' . $bc . '"' ?><?php if ($bi = Horde_Util::nonInputVar('bodyId')) echo ' id="' . $bi . '"'; ?>>
diff --git a/beatnik/templates/listzones/footer.inc b/beatnik/templates/listzones/footer.inc
new file mode 100644 (file)
index 0000000..cd37a4b
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+/**
+ * $Horde: beatnik/templates/listzones/footer.inc,v 1.6 2008/08/22 09:00:22 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+?>
+</tbody>
+</table>
+<?php echo $pager->render(); ?>
diff --git a/beatnik/templates/listzones/header.inc b/beatnik/templates/listzones/header.inc
new file mode 100644 (file)
index 0000000..62e0b98
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/**
+ * $Horde: beatnik/templates/listzones/header.inc,v 1.13 2009/07/15 15:05:33 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ */
+?>
+<div class="box" style="float: right;">
+<p>
+<?php
+echo _("Domain Categories") . ' ';
+if (Horde_Auth::getAuth() && (!$prefs->isLocked('categories') || !$prefs->isLocked('category_colors'))) {
+    $categoryUrl = Horde_Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/prefs.php'),
+                                        array('app' => 'horde', 'group' => 'categories'));
+    echo Horde::link($categoryUrl, _("Edit domain groups and colors"), 'widget', '_blank') .
+         Horde::img('colorpicker.png', _("Edit categories and colors"), array('align' => 'absmiddle'), $registry->getImageDir('horde')) . '</a>';
+}
+?>
+</p>
+<p>
+<ul>
+    <?php
+    foreach ($categories as $category) {
+        $color = isset($colors[$category]) ? $colors[$category] : '#FFFFFF';
+        $fgcolor = isset($fgcolors[$category]) ? $fgcolors[$category] : '#000000';
+    ?>
+        <li style="background-color:<?php echo htmlspecialchars($color) ?>; color:<?php echo $fgcolor ?>">
+            <input type="checkbox" id="dgroup-<?php echo $category; ?>" onclick="javascript:groupToggle('<?php echo $category; ?>');" />
+            <?php echo $category; ?>
+        </li>
+    <?php
+    }
+    ?>
+</ul>
+</p>
+</div>
+
+<table class="striped">
+<thead>
+<tr>
+<th><?php echo _("Actions")?></th>
+<?php
+foreach ($fields as $field) {
+    echo '<th>' . $field['name'] . '</th>';
+}
+?>
+</tr>
+</thead>
+<tbody>
+
diff --git a/beatnik/templates/listzones/row.inc b/beatnik/templates/listzones/row.inc
new file mode 100644 (file)
index 0000000..7146d39
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * $Horde: beatnik/templates/listzones/row.inc,v 1.11 2007/06/27 17:23:31 jan Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ */
+?>
+<tr>
+<td>
+    <?php 
+        echo Horde::link($autourl) . 
+             Horde::img('devel.png', _("Autogenerate"), '', $img_dir) . '</a> ' . 
+             Horde::link($deleteurl) . 
+             Horde::img('delete.png', _("Delete"), '', $img_dir) . '</a> ' . 
+             Horde::link($editurl) . 
+             Horde::img('edit.png', _("Edit"), '', $img_dir) . '</a>';
+    ?>
+</td>
+<?php 
+foreach ($fields as $key => $field) {
+    if ($key == 'zonename') {
+        echo '<td>' . Horde::link($viewurl) . $domain['zonename'] . '</a></td>';
+    } else {
+        echo '<td>' . $domain[$key] . '</td>';
+    }
+}
+?>
+</tr>
diff --git a/beatnik/templates/menu.inc b/beatnik/templates/menu.inc
new file mode 100644 (file)
index 0000000..172f9ca
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/*
+ * $Horde: beatnik/templates/menu.inc,v 1.18 2009/07/15 15:08:27 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+$accesskey = $prefs->getValue('widget_accesskey') ? Horde::getAccessKey(_("Select _Domain")) : '';
+$menu_view = $prefs->getValue('menu_view');
+?>
+<div id="menu">
+<span style="float:right">
+<?php
+    $link = Horde::link('#', _("Select Domain"), '', '', 'domainSubmit(true); return false;');
+    sprintf('<ul><li>%s%s<br />%s</a></li></ul>', $link, Horde::img('tree/folderopen.png', '', '', $registry->getImageDir('horde')), ($menu_view != 'icon') ? Horde::highlightAccessKey(_("Select _Domain"), $accesskey) : '');
+?>
+</span>
+<span style="float:right">
+    <form action="<?php echo Horde::applicationUrl('viewzone.php') ?>" method="get" name="menu">
+        <?php Horde_Util::pformInput(); ?>
+        <label for="domain" accesskey="<?php echo $accesskey ?>">
+            <select id="domainSelector" name="curdomain"
+                onchange="domainSubmit()">
+                <?php
+                    foreach ($domains as $domain) {
+                        echo '<option value="' . $domain['zonename'] . '"';
+                        if ($domain['zonename'] == $_SESSION['beatnik']['curdomain']['zonename']) {
+                            echo " selected";
+                        }
+                        echo '>' . $domain['zonename'] . "</option>\n";
+                    }
+                ?>
+            </select>
+        </label>
+    </form>
+</span>
+<?php echo Beatnik::getMenu('string') ?>
+</div>
+<?php $GLOBALS['notification']->notify(array('listeners' => 'status')) ?>
diff --git a/beatnik/templates/view/footer.inc b/beatnik/templates/view/footer.inc
new file mode 100644 (file)
index 0000000..02dda0e
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * $Horde: beatnik/templates/view/footer.inc,v 1.4 2008/08/22 09:00:23 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+?>
+</tbody>
+</table>
diff --git a/beatnik/templates/view/header.inc b/beatnik/templates/view/header.inc
new file mode 100644 (file)
index 0000000..78b63f4
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/**
+ * $Horde: beatnik/templates/view/header.inc,v 1.14 2009/07/03 10:05:31 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+if (Beatnik::needCommit($_SESSION['beatnik']['curdomain']['zonename'])) {
+    $commit = Horde_Util::addParameter(Horde::applicationUrl('commit.php'), array('domain' => 'current'));
+    echo Horde::link($commit, _('Commit Changes'), 'button') . _('Commit Changes') . '</a><br /><br />';
+}
+?>
+
+<div class="header">
+<?php
+$params = array('id' => $_SESSION['beatnik']['curdomain']['id'],
+                'rectype' => 'soa');
+echo $_SESSION['beatnik']['curdomain']['zonename'] . '&nbsp;'
+     . Horde::link(Horde_Util::addParameter($edit, $params)) 
+     . Horde::img('edit.png', _("Edit"), '', $img_dir) . '</a> ' 
+     . Horde::link(Horde_Util::addParameter($delete, $params)) 
+     . Horde::img('delete.png', _("Delete"), '', $img_dir) . '</a></td>';
+?>
+</div>
+
+<?php
+if ($_SESSION['beatnik']['expertmode']) {
+    echo '<table class="striped">' . "\n";
+    foreach (Beatnik::getRecFields('soa') as $key => $value) {
+        if (isset($_SESSION['beatnik']['curdomain'][$key])) {
+            echo '<tr><td>' . $value['name'] . '</td><td>' . $_SESSION['beatnik']['curdomain'][$key] . '</td></tr>' . "\n";
+        }
+    }
+    echo '</table><br />' . "\n";
+}
+?>
+<table class="striped">
+<thead>
+<tr>
+    <th><?php echo _("Actions")?></th>
+    <th><?php echo _("Type") ?></th>
+    <?php
+    foreach ($fields as $field => $fdata) {
+        if ((($fdata['infoset'] == 'basic') || $_SESSION['beatnik']['expertmode']) && $fdata['type'] != 'hidden') {
+            echo '<th>' . $fdata['name'] . '</th>' . "\n";
+        }
+    }
+    ?>
+</tr>
+</thead>
+<tbody>
diff --git a/beatnik/templates/view/record.inc b/beatnik/templates/view/record.inc
new file mode 100644 (file)
index 0000000..489e8b5
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * $Horde: beatnik/templates/view/record.inc,v 1.13 2009/07/03 10:05:31 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+foreach ($zonedata[$type] as $record) {
+
+    $params = array('id' => $record['id'], 'rectype' => $type);
+    echo '<tr>' . "\n";
+    echo '<td>' . Horde::link(Horde_Util::addParameter($edit, $params)) 
+                . Horde::img('edit.png', _("Edit"), '', $img_dir) . '</a> ' 
+                . Horde::link(Horde_Util::addParameter($delete, $params)) 
+                . Horde::img('delete.png', _("Delete"), '', $img_dir) . '</a></td>';
+    echo '<td>' . $rectypes[$type] . '</td>';
+
+    foreach ($fields as $field => $fdata) {
+        
+        if ((($fdata['infoset'] != 'basic') && !$_SESSION['beatnik']['expertmode']) || $fdata['type'] == 'hidden') {
+            continue;
+        }
+
+        echo '<td>' . "\n";
+        
+        if (!isset($record[$field])) {
+            continue;
+        }
+
+        if (is_array($record[$field])) {
+            foreach ($record[$field] as $entry) {
+                echo $entry;
+            }
+        } else {
+            echo $record[$field];
+        }
+
+        echo '</td>' . "\n";
+    }
+
+    echo '</tr>' . "\n";
+}
diff --git a/beatnik/themes/graphics/beatnik.png b/beatnik/themes/graphics/beatnik.png
new file mode 100644 (file)
index 0000000..7020eb9
Binary files /dev/null and b/beatnik/themes/graphics/beatnik.png differ
diff --git a/beatnik/themes/graphics/commit-all.png b/beatnik/themes/graphics/commit-all.png
new file mode 100644 (file)
index 0000000..5237dab
Binary files /dev/null and b/beatnik/themes/graphics/commit-all.png differ
diff --git a/beatnik/themes/screen.css b/beatnik/themes/screen.css
new file mode 100644 (file)
index 0000000..c7b4f19
--- /dev/null
@@ -0,0 +1,14 @@
+/**
+ * $Horde: beatnik/themes/screen.css,v 1.5 2006/12/18 05:56:52 chuck Exp $
+ */
+
+.zoneData {
+    border: 1px solid #000;
+    padding: 2px;
+}
+
+.soa {
+    border: 1px solid #111;
+    padding: 2px;
+    font-size: 120%
+}
diff --git a/beatnik/viewzone.php b/beatnik/viewzone.php
new file mode 100644 (file)
index 0000000..a4459e7
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * $Horde: beatnik/viewzone.php,v 1.21 2009/07/03 10:05:29 duck Exp $
+ *
+ * Copyright 2005-2007 Ben Klang <ben@alkaloid.net>
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+define('BEATNIK_BASE', dirname(__FILE__));
+require_once BEATNIK_BASE . '/lib/base.php';
+require_once BEATNIK_BASE . '/lib/Beatnik.php';
+
+$zonedata = $beatnik_driver->getRecords($_SESSION['beatnik']['curdomain']['zonename']);
+if (is_a($zonedata, 'PEAR_Error')) {
+    $notification->push($zonedata, 'horde.error');
+    header('Location:' . Horde::applicationUrl('listzones.php'));
+    exit;
+}
+
+$title = $_SESSION['beatnik']['curdomain']['zonename'];
+Horde::addScriptFile('stripe.js', 'horde', true);
+require BEATNIK_TEMPLATES . '/common-header.inc';
+require BEATNIK_TEMPLATES . '/menu.inc';
+
+// Get a list of all the fields for all record typess we'll be processing
+$fields = array();
+foreach ($zonedata as $type => $data) {
+    $fields = array_merge($fields, Beatnik::getRecFields($type));
+}
+
+// Remove fields that should not be shown
+foreach ($fields as $field_id => $field) {
+    if ($field['type'] == 'hidden' ||
+        ($field['infoset'] != 'basic' && !$_SESSION['beatnik']['expertmode'])) {
+        unset($field[$field_id]);
+    }
+}
+
+$img_dir = $registry->getImageDir('horde');
+$delete = Horde_Util::addParameter(Horde::applicationUrl('delete.php'), 'curdomain', $_SESSION['beatnik']['curdomain']['zonename']);
+$edit = Horde_Util::addParameter(Horde::applicationUrl('editrec.php'), 'curdomain', $_SESSION['beatnik']['curdomain']['zonename']);
+$rectypes = Beatnik::getRecTypes();
+
+require BEATNIK_TEMPLATES . '/view/header.inc';
+foreach ($rectypes as $type => $typedescr) {
+    if (!isset($zonedata[$type])) {
+        continue;
+    }
+    require BEATNIK_TEMPLATES . '/view/record.inc';
+}
+require BEATNIK_TEMPLATES . '/view/footer.inc';
+
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';