Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Yalçın Can 2011-07-27 14:23:37 +03:00
commit efd842174f
52 changed files with 1901 additions and 325 deletions

166
build.xml
View file

@ -9,18 +9,31 @@
<property file="build.properties" /> <property file="build.properties" />
<property file="html.properties" /> <property file="html.properties" />
<property name="version-m" value="0.97" /> <property name="version-m" value="0.98" />
<property name="version" value="0.97.0" /> <property name="version" value="0.98.0" />
<property name="stability" value="beta" /> <property name="stability" value="beta" />
<property name="releasenotes" value="- Many SQL optimizations <property name="releasenotes" value="- Switch to jQuery and drop dojo
- SemanticScuttle shows bookmarks 4 times faster now - Implement request #2928950: Private keys for RSS feeds (Mark Pemberton)
- New config option to skip 'SET NAMES UTF8' call: $dbneedssetnames - Implement request #3164348: Configurable default privacy (Brett Dee)
- Do not highlight admin bookmarks when $enableAdminColors is disabled - Implement request #1989987: Theming support
- Add russian translation - Implement request #3054906: Show user's full name instead of nickname
- Make HTML export follow the specifications a bit better - Implement patch #3059829: update ``FR_CA`` translation
- Fix bug #2953732: faulty error message for duplicate bookmarks - Fix bug #3187177: Wrong URL / Export XML Bookmarks
- Fix bug #2960663: do not send content-type headers twice for ajax/api scripts - Fix bug #3097187: Using opensearch with two tags does not work in Firefox
- Fix bug #2976593: fr_FR locale is vietnamese - Fix bug #3251877: French translation JavaScript Bug when editing bookmarks
- Fix bug #3168521: Title of tag-filtered RSS Feed is broken
- Fix bug #2853627: Javascript warning
- Fix bug in ``getTagsForBookmarks()`` that fetched all tags
- Fix privacy issue when fetching tags of several users
- Fix Google custom search XML
- Show error message on mysqli connection errors
- Update php-gettext library to 1.0.10
- ``api/posts/add`` respects the 'replace' parameter now
- Only URLs with an allowed protocol may be added to the database
- Support HTTPS connections when ``$root`` is not configured
- SQL schema version table to ease future database upgrades
- Documentation is written with rST (reStructuredText) now
- Support per-host configuration files
" /> " />
<property name="zipfile" value="${phing.project.name}-${version}.zip" /> <property name="zipfile" value="${phing.project.name}-${version}.zip" />
<property name="pkgfile" value="${phing.project.name}-${version}.tgz" /> <property name="pkgfile" value="${phing.project.name}-${version}.tgz" />
@ -30,7 +43,8 @@
<property name="sffilepath" value="s/se/semanticscuttle/" /> <property name="sffilepath" value="s/se/semanticscuttle/" />
<property name="svnpath" value="https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/" /> <property name="svnpath" value="https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/" />
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" /> <taskdef name="rST" classname="phing.tasks.ext.rSTTask" />
<taskdef name="d51pearpkg2" classname="phing.tasks.ext.d51PearPkg2Task" />
<target name="zip" depends="check" <target name="zip" depends="check"
description="Create zip file for release" description="Create zip file for release"
@ -52,11 +66,12 @@
<exclude name="**/.gitignore/"/> <exclude name="**/.gitignore/"/>
<exclude name="**/.svn/"/> <exclude name="**/.svn/"/>
<exclude name="data/config.php"/> <exclude name="data/config.php"/>
<exclude name="data/config.testing.php"/>
<exclude name="data/config.testing-tmp.php"/>
<exclude name="data/locales/messages.po"/> <exclude name="data/locales/messages.po"/>
<exclude name="data/locales/*/LC_MESSAGES/messages.po"/> <exclude name="data/locales/*/LC_MESSAGES/messages.po"/>
<exclude name="doc/developers/"/>
<exclude name="src/php-gettext/examples/" /> <exclude name="src/php-gettext/examples/" />
<exclude name="src/php-gettext/bin/"/> <exclude name="src/php-gettext/tests/"/>
</fileset> </fileset>
</zip> </zip>
</target> </target>
@ -66,7 +81,6 @@
<target name="package" depends="check" <target name="package" depends="check"
description="Creates the pear package" description="Creates the pear package"
> >
<!-- fixme: create package.xml with d51pearpkg2 -->
<d51pearpkg2 dir="." baseinstalldir="/"> <d51pearpkg2 dir="." baseinstalldir="/">
<name>SemanticScuttle</name> <name>SemanticScuttle</name>
<summary>A social bookmarking tool</summary> <summary>A social bookmarking tool</summary>
@ -108,30 +122,34 @@
<ignore>**/.svn</ignore> <ignore>**/.svn</ignore>
<ignore>build*</ignore> <ignore>build*</ignore>
<ignore>data/config.php</ignore> <ignore>data/config.php</ignore>
<ignore>data/config.testing.php</ignore>
<ignore>data/config.testing-tmp.php</ignore>
<ignore>data/locales/messages.po</ignore> <ignore>data/locales/messages.po</ignore>
<ignore>data/locales/*/LC_MESSAGES/messages.po</ignore> <ignore>data/locales/*/LC_MESSAGES/messages.po</ignore>
<ignore>dist/**</ignore> <ignore>dist/**</ignore>
<ignore>doc/developers/**</ignore>
<ignore>scripts/**</ignore> <ignore>scripts/**</ignore>
<ignore>src/php-gettext/examples/**</ignore> <ignore>src/php-gettext/examples/**</ignore>
<ignore>src/php-gettext/bin/**</ignore> <ignore>src/php-gettext/tests/**</ignore>
<ignore>*.tgz</ignore>
<ignore>*.properties</ignore> <ignore>*.properties</ignore>
<ignore>semanticscuttle-dump.sql</ignore>
<ignore>*.tgz</ignore>
<replacement <replacement path="src/SemanticScuttle/header.php"
path="src/SemanticScuttle/header.php"
type="pear-config" from="@data_dir@" to="data_dir" type="pear-config" from="@data_dir@" to="data_dir"
/> />
<replacement <replacement path="src/SemanticScuttle/header.php"
path="src/SemanticScuttle/header.php"
type="pear-config" from="@www_dir@" to="www_dir" type="pear-config" from="@www_dir@" to="www_dir"
/> />
<replacement <replacement path="src/SemanticScuttle/Config.php"
path="www/www-header.php"
type="pear-config" from="@data_dir@" to="data_dir" type="pear-config" from="@data_dir@" to="data_dir"
/> />
<replacement <replacement path="src/SemanticScuttle/Config.php"
path="tests/prepare.php" type="pear-config" from="@www_dir@" to="www_dir"
/>
<replacement path="www/www-header.php"
type="pear-config" from="@data_dir@" to="data_dir"
/>
<replacement path="tests/prepare.php"
type="pear-config" from="@data_dir@" to="data_dir" type="pear-config" from="@data_dir@" to="data_dir"
/> />
@ -207,49 +225,31 @@
</target> </target>
<!-- you need to have the python docutils package installed, since <target name="build-docs" description="render documentation">
we use the rst2html tool --> <rST format="html" uptodate="true"
<target name="build-docs"> toolparam="--stylesheet=res/docs/style.css"
<foreach param="fname" absparam="abs-fname" target="build-doc-file">
<fileset dir=".">
<include name="doc/ChangeLog"/>
<include name="doc/**.txt"/>
<include name="doc/**.rst"/>
<include name="doc/**/*.rst"/>
<exclude name="doc/LICENSE.txt"/>
<exclude name="doc/developers/TODO.rst"/>
</fileset>
</foreach>
</target>
<target name="build-doc-file" depends="check"
description="Builds a single documentation file. Pass file path as $fname"
> >
<echo msg="${fname}"/> <fileset dir="doc">
<php function="preg_replace" returnProperty="outfile"> <include name="ChangeLog"/>
<param value="/^(.+)(.rst|.txt)$/"/> <include name="**.txt"/>
<param value="dist/\1.html"/> <include name="**.rst"/>
<param value="${fname}"/> <include name="**/*.rst"/>
</php> <exclude name="LICENSE.txt"/>
<exclude name="developers/TODO.rst"/>
<!-- only render file if the doc file is newer than the html file --> <exclude name="allinone.rst"/>
<property name="isuptodate" value="false"/> </fileset>
<uptodate property="isuptodate" srcfile="${fname}" targetfile="${outfile}" /> <mapper type="regexp" from="^doc/(.+?)(.rst|.txt)?$" to="dist/docs/\1.html"/>
<if> <!--
<not><istrue value="${isuptodate}"/></not> <filterchain>
<then> <replacetokenswithfile dir="res/docs">
<exec <
command="rst2html --exit-status=2 ${fname} ${outfile}" </replacetokenswithfile>
checkreturn="1" </filterchain>
/> -->
</then> </rST>
</if>
</target> </target>
<target name="release" depends="check,zip,package,deploy-sf" <target name="release" depends="check,zip,package,deploy-sf"
description="Release the version on sourceforge" description="Release the version on sourceforge"
> >
@ -284,7 +284,7 @@
<target name="deploy-sf-pear" depends="check,package" <target name="deploy-sf-pear" depends="check,package"
description="Update PEAR channel on sourceforge" description="Update PEAR channel + website on sourceforge"
> >
<available file="${websitedir}" <available file="${websitedir}"
type="dir" property="available.websitedir" type="dir" property="available.websitedir"
@ -349,6 +349,38 @@
</target> </target>
<target name="deploy-docs" depends="build-docs"
description="sync docs to sourceforge website"
>
<copy todir="${websitedir}/docs">
<fileset dir="dist/docs">
<include name="**"/>
</fileset>
</copy>
<exec
command="rsync -avP -e ssh . ${sfuser},${sfproject}@web.sourceforge.net:htdocs/docs"
dir="${websitedir}/docs"
escape="false" checkreturn="false"
passthru="true"
/>
</target>
<target name="deploy-demo"
description="sync demo to sourceforge website"
>
<exec
command="rsync -avP -e ssh . ${sfuser},${sfproject}@web.sourceforge.net:htdocs/demo"
dir="${websitedir}/demo"
escape="false" checkreturn="false"
passthru="true"
/>
</target>
<target name="check" <target name="check"
description="Check variables" description="Check variables"
> >
@ -358,8 +390,6 @@
<fail unless="sffilepath" message="Sourceforge project file path not defined!" /> <fail unless="sffilepath" message="Sourceforge project file path not defined!" />
<mkdir dir="dist" /> <mkdir dir="dist" />
<mkdir dir="dist/doc" />
<mkdir dir="dist/doc/developers" />
</target> </target>
</project> </project>

View file

@ -13,3 +13,7 @@ CREATE TABLE `sc_users_sslclientcerts` (
PRIMARY KEY ( `id` ) , PRIMARY KEY ( `id` ) ,
UNIQUE (`id`) UNIQUE (`id`)
) CHARACTER SET utf8 COLLATE utf8_general_ci; ) CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `sc_users` ADD `privateKey` VARCHAR(33) NULL;
CREATE UNIQUE INDEX `privateKey` ON `sc_users` (`privateKey`);

View file

@ -72,7 +72,9 @@ CREATE TABLE `sc_users` (
`email` varchar(50) NOT NULL default '', `email` varchar(50) NOT NULL default '',
`homepage` varchar(255) default NULL, `homepage` varchar(255) default NULL,
`uContent` text, `uContent` text,
PRIMARY KEY (`uId`) `privateKey` varchar(33) default NULL,
PRIMARY KEY (`uId`),
UNIQUE KEY `privateKey` (`privateKey`)
) CHARACTER SET utf8 COLLATE utf8_general_ci ; ) CHARACTER SET utf8 COLLATE utf8_general_ci ;
-- -------------------------------------------------------- -- --------------------------------------------------------
@ -84,8 +86,7 @@ CREATE TABLE `sc_users_sslclientcerts` (
`sslClientIssuerDn` VARCHAR( 1024 ) NOT NULL , `sslClientIssuerDn` VARCHAR( 1024 ) NOT NULL ,
`sslName` VARCHAR( 64 ) NOT NULL , `sslName` VARCHAR( 64 ) NOT NULL ,
`sslEmail` VARCHAR( 64 ) NOT NULL , `sslEmail` VARCHAR( 64 ) NOT NULL ,
PRIMARY KEY ( `id` ) , PRIMARY KEY ( `id` )
UNIQUE (`id`)
) CHARACTER SET utf8 COLLATE utf8_general_ci; ) CHARACTER SET utf8 COLLATE utf8_general_ci;
-- --

View file

@ -17,7 +17,7 @@ $this->includeTemplate($GLOBALS['top_include']);
<?php if(!is_null($currentUser) && $currentUser->isAdmin()): ?> <?php if(!is_null($currentUser) && $currentUser->isAdmin()): ?>
<li>SemanticScuttle v0.97.0</li> <li>SemanticScuttle v0.98.0</li>
<?php endif ?> <?php endif ?>
</ul> </ul>

View file

@ -1,35 +0,0 @@
<?php
/***************************************************************************
Copyright (C) 2005 - 2006 Scuttle project
http://sourceforge.net/projects/scuttle/
http://scuttle.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
***************************************************************************/
?>
<?php if (isset($loadjs)) :?>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/dojo/1.2/dojo/dojo.xd.js"
djConfig="parseOnLoad:true, isDebug:<?php echo DEBUG_MODE?'true':'false' ?>, usePlainJson:true, baseUrl: '<?php echo ROOT ?>', modulePaths: {'js': 'js'}"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("js.MultiComboBox"); // DOJO module adapted for SemanticScuttle
dojo.require("dijit.Tree");
</script>
<?php endif ?>

View file

@ -28,6 +28,14 @@ $this->includeTemplate($GLOBALS['top_include']);
<td><input type="text" name="pMail" size="75" value="<?php echo filter($objectUser->getEmail(), 'xml'); ?>" /></td> <td><input type="text" name="pMail" size="75" value="<?php echo filter($objectUser->getEmail(), 'xml'); ?>" /></td>
<td> <?php echo T_('Required'); ?></td> <td> <?php echo T_('Required'); ?></td>
</tr> </tr>
<tr>
<th align="left"><?php echo T_('Private RSS Feed'); ?></th>
<td><input type="checkbox" id="pEnablePrivateKey" name="pEnablePrivateKey" value="true" <?php echo $privateKeyIsEnabled;?> />
<label for="pEnablePrivateKey"><?php echo T_('Enable'); ?></label>&nbsp;&nbsp;&nbsp;
<input type="text" id="pPrivateKey" name="pPrivateKey" size="40" value="<?php echo $privateKey;?>" readonly="readonly" />
<a onclick="getNewPrivateKey(this); return false;"><button type="submit" name="submittedPK" value="1"><?php echo T_('Generate New Key'); ?></button></a>
</td>
</tr>
</table> </table>
<h3><?php echo T_('Personal Details'); ?></h3> <h3><?php echo T_('Personal Details'); ?></h3>

View file

@ -3,7 +3,7 @@ echo '<' . '?xml version="1.0" encoding="utf-8" ?' . ">\n";
?> ?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"> <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel> <channel>
<title><?php echo htmlspecialchars($feedtitle); ?></title> <title><?php echo $feedtitle; ?></title>
<link><?php echo htmlspecialchars($feedlink); ?></link> <link><?php echo htmlspecialchars($feedlink); ?></link>
<description><?php echo htmlspecialchars($feeddescription); ?></description> <description><?php echo htmlspecialchars($feeddescription); ?></description>
<pubDate><?php echo date('r'); ?></pubDate> <pubDate><?php echo date('r'); ?></pubDate>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?php echo '<'; ?>?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head> <head>
@ -13,7 +13,7 @@ if (isset($rsschannels)) {
for ($i = 0; $i < $size; $i++) { for ($i = 0; $i < $size; $i++) {
echo ' <link rel="alternate" type="application/rss+xml" title="' echo ' <link rel="alternate" type="application/rss+xml" title="'
. htmlspecialchars($rsschannels[$i][0]) . '"' . htmlspecialchars($rsschannels[$i][0]) . '"'
. ' href="'. $rsschannels[$i][1] .'" />'; . ' href="'. htmlspecialchars($rsschannels[$i][1]) .'" />' . "\n";
} }
} }
?> ?>

View file

@ -12,7 +12,7 @@ if (isset($rsschannels)) {
$size = count($rsschannels); $size = count($rsschannels);
for ($i = 0; $i < $size; $i++) { for ($i = 0; $i < $size; $i++) {
echo ' <link rel="alternate" type="application/rss+xml" title="' echo ' <link rel="alternate" type="application/rss+xml" title="'
. htmlspecialchars($rsschannels[$i][0]) . '"' . $rsschannels[$i][0] . '"'
. ' href="'. $rsschannels[$i][1] .'" />'; . ' href="'. $rsschannels[$i][1] .'" />';
} }
} }

View file

@ -1,28 +1,39 @@
ChangeLog for SemantiScuttle ChangeLog for SemantiScuttle
============================ ============================
.. contents::
0.98.0 - 2011-XX-XX 0.98.1 - 2011-XX-XX
-------------------
- Fix bug #3375635: XML parsing problem in top.inc.php
- Fix bug #3375428: Forgot to remove some old dojo files
- Fix bug #3160512: Make SemanticScuttle work with FastCGI
0.98.0 - 2011-07-21
------------------- -------------------
- Switch to jQuery and drop dojo - Switch to jQuery and drop dojo
- Implement request #2928950: Private keys for RSS feeds (Mark Pemberton)
- Implement request #3164348: Configurable default privacy (Brett Dee)
- Implement request #1989987: Theming support
- Implement request #3054906: Show user's full name instead of nickname
- Implement patch #3059829: update ``FR_CA`` translation
- Fix bug #3187177: Wrong URL / Export XML Bookmarks - Fix bug #3187177: Wrong URL / Export XML Bookmarks
- Fix bug in ``getTagsForBookmarks()`` that fetched all tags
- Fix bug #3097187: Using opensearch with two tags does not work in Firefox - Fix bug #3097187: Using opensearch with two tags does not work in Firefox
- Fix bug #3251877: French translation JavaScript Bug when editing bookmarks - Fix bug #3251877: French translation JavaScript Bug when editing bookmarks
- Fix bug #3168521: Title of tag-filtered RSS Feed is broken - Fix bug #3168521: Title of tag-filtered RSS Feed is broken
- Fix bug #2853627: Javascript warning - Fix bug #2853627: Javascript warning
- Implement request #1989987: Theming support - Fix bug in ``getTagsForBookmarks()`` that fetched all tags
- Implement request #3054906: Show user's full name instead of nickname - Fix privacy issue when fetching tags of several users
- Implement patch #3059829: update ``FR_CA`` translation - Fix Google custom search XML
- Show error message on mysqli connection errors - Show error message on mysqli connection errors
- Update php-gettext library to 1.0.10 - Update php-gettext library to 1.0.10
- ``api/posts/add`` respects the "replace" parameter now - ``api/posts/add`` respects the "replace" parameter now
- Fix privacy issue when fetching tags of several users
- Fix Google custom search XML
- Only URLs with an allowed protocol may be added to the database - Only URLs with an allowed protocol may be added to the database
- Support HTTPS connections when ``$root`` is not configured - Support HTTPS connections when ``$root`` is not configured
- SQL schema version table to ease future database upgrades - SQL schema version table to ease future database upgrades
- Documentation is written with rST (reStructuredText) now - Documentation is written with rST (reStructuredText) now
- Support per-host configuration files
0.97.2 - 2011-02-17 0.97.2 - 2011-02-17

View file

@ -22,7 +22,7 @@ Installation instructions
on the shell ("semanticscuttle" being the database name) on the shell ("semanticscuttle" being the database name)
3. Copy ``data/config.php.dist`` to ``data/config.php`` and modify it as 3. Copy ``data/config.php.dist`` to ``data/config.php`` and modify it as
necessary. necessary. See configuration_ for more information.
4. Make the cache directory writable by your web server. 4. Make the cache directory writable by your web server.
For example, run :: For example, run ::
@ -31,6 +31,12 @@ Installation instructions
on the shell. on the shell.
5. Set the ``www/`` directory as document root in your web server, 5. Set the ``www/`` directory as document root in your web server,
restart the web server. restart the web server.
6. That's all! Visit your SemanticScuttle installation web site now
with your browser.
7. Register a user and add bookmarks.
.. _configuration: configuration.html
Ugly www directory in URLs Ugly www directory in URLs

View file

@ -4,7 +4,9 @@ SemanticScuttle 0.98
A social bookmarking tool experimenting with new features A social bookmarking tool experimenting with new features
like structured tags or collaborative descriptions of tags. like structured tags or collaborative descriptions of tags.
https://sourceforge.net/projects/semanticscuttle/ - Home page: http://semanticscuttle.sourceforge.net/
- Project page: https://sourceforge.net/projects/semanticscuttle/
- Demo: http://semanticscuttle.sourceforge.net/demo/
Available under the GNU General Public License Available under the GNU General Public License

View file

@ -2,6 +2,7 @@
Upgrading SemanticScuttle from a previous version Upgrading SemanticScuttle from a previous version
================================================= =================================================
.. contents::
From version 0.97 to 0.98 From version 0.97 to 0.98
========================= =========================
@ -9,6 +10,9 @@ Database updates
---------------- ----------------
Apply ``data/schema/6.sql`` Apply ``data/schema/6.sql``
ALTER TABLE `sc_users` ADD `privateKey` VARCHAR(33) NULL;
CREATE UNIQUE INDEX `privateKey` ON `sc_users` (`privateKey`);
From version 0.96 to 0.97 From version 0.96 to 0.97
========================= =========================

31
doc/allinone.rst Normal file
View file

@ -0,0 +1,31 @@
=============================
SemanticScuttle documentation
=============================
.. contents::
First reads
===========
.. include:: README.rst
.. include:: INSTALL.txt
.. include:: UPGRADE.txt
Features
========
.. include:: authentication.rst
.. include:: ssl-client-certificates.rst
.. include:: themes.rst
Developer documentation
=======================
.. include:: developers/rules.rst
.. include:: developers/api.rst
.. include:: developers/debugging.rst
.. include:: developers/release-new-version.rst
.. include:: developers/running-unit-tests.rst
.. include:: developers/translation.rst
.. include:: ChangeLog

58
doc/configuration.rst Normal file
View file

@ -0,0 +1,58 @@
===================
Configuration files
===================
SemanticScuttle uses at least two configuration files:
1. Default configuration file ``config.default.php``
2. Custom configuration file ``config.php``
The **default configuration** file contains sensible defaults for most users
that do not need to be changed to get started.
Never change it - it will get overwritten with the next update.
If you want to change values in it, copy them into your personal
``config.php`` file - updates to SemanticScuttle will not change that one.
The **custom configuration** file, ``config.php`` is created by copying the
shipped ``config.php.dist`` file and modifying the values in there.
It consists of the configuration directives that should be set on every
fresh installation.
Configuration scenarios
=======================
Simple installation
-------------------
Put your configuration file in ``data/config.php``.
If you installed SemanticScuttle's PEAR package, use::
$ pear config-get data_dir
/usr/share/php/data
to find the data directory location and append ``SemanticScuttle/`` to it.
In this case, the configuration file has to be in::
/usr/share/php/data/SemanticScuttle/config.php
The configuration file may also be saved into::
/etc/semanticscuttle/config.php
Multiple SemanticScuttle instances
----------------------------------
The files of one single SemanticScuttle installation may be shared
for several SemanticScuttle instances.
To be able to configure them differently, SemanticScuttle supports
per-host configuration files:
- ``data/config.$hostname.php``
- ``/etc/semanticscuttle/config.$hostname.php``

View file

@ -2,18 +2,18 @@ How to release a new version of SemanticScuttle
=============================================== ===============================================
0. Run unit tests and verify that all of them pass 0. Run unit tests and verify that all of them pass
1. Update doc/ChangeLog 1. Update ``doc/ChangeLog``
2. Update doc/UPGRADE.txt 2. Update ``doc/UPGRADE.txt``
3. Update version in data/templates/about.tpl.php, 3. Update version in ``data/templates/about.tpl.php``,
build.xml and doc/README.txt ``build.xml`` and ``doc/README.rst``
4. Create a release zip file via the build script: 4. Create a release zip file via the build script:
Just type "phing". Just type "``phing``".
5. Make a test installation from your zip file with a fresh 5. Make a test installation from your zip file with a fresh
database. Register a user, add bookmarks etc. database. Register a user, add bookmarks etc.
6. When all is fine, it's time to release. 6. When all is fine, it's time to release.
The build script takes care for most of the The build script takes care for most of the
tasks. tasks.
Run "phing release", and it will upload the release to Run "``phing release``", and it will upload the release to
sourceforge and create a svn tag. sourceforge and create a svn tag.
7. Write announcement mail to the SemanticScuttle mailing list 7. Write announcement mail to the SemanticScuttle mailing list
semanticscuttle-devel@lists.sourceforge.net semanticscuttle-devel@lists.sourceforge.net

View file

@ -20,10 +20,12 @@ Features
- `Custom user authentication`__ - `Custom user authentication`__
- `SSL Client certificates`__ - `SSL Client certificates`__
- Themes__ - Themes__
- `Configuration files`__
__ authentication.html __ authentication.html
__ ssl-client-certificates.html __ ssl-client-certificates.html
__ themes.html __ themes.html
__ configuration.html
@ -35,6 +37,7 @@ Developer documentation
- `How to release a new version`__ - `How to release a new version`__
- `Running unit testes`__ - `Running unit testes`__
- `How to translate SemanticScuttle`__ - `How to translate SemanticScuttle`__
- `ChangeLog`__
__ developers/rules.html __ developers/rules.html
__ developers/api.html __ developers/api.html
@ -42,4 +45,4 @@ __ developers/debugging.html
__ developers/release-new-version.html __ developers/release-new-version.html
__ developers/running-unit-tests.html __ developers/running-unit-tests.html
__ developers/translation.html __ developers/translation.html
__ ChangeLog.html

8
res/docs/header.tpl.html Normal file
View file

@ -0,0 +1,8 @@
<ul class="menu">
<li><a href="index.html">Index</a></li>
<li><a href="INSTALL.html">Installation</a></li>
<li><a href="UPGRADE.html">Upgrade</a></li>
<li><a href="ChangeLog.html">ChangeLog</a></li>
<li><a href="http://semanticscuttle.sourceforget.net/">Homepage</a></li>
<li><a href="https://sourceforget.net/projects/semanticscuttle">Project page</a></li>
</ul>

25
res/docs/style.css Normal file
View file

@ -0,0 +1,25 @@
/* SemanticScuttle improvements */
h1.title {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAsvSURBVGiBxZprjCRXdcd/p6r6Nd0979mHZ+3sCkfx4AdJsAhGm2BMYhAbrAgsEimKjGIlxELk8SkrQoQiSGgrCSJZh9hECsQ8hJCQlRDjYDsY4RAvRF6b9drN2t7HzOzOzmtnpl/V1fW4Jx+qu6emZ3pmbI/kI7Wq+9Ste///87j33FstqspeS7kkR4HPAAeAx4G/mDqu9T0fCJC9JlAuyXuB72Ymbklnxt9K/fxjmFblf4A7p45rc08HA6y97KxcksPAI5mJW9JDN99DbvI2Rt7+CSSVPwo8tJdjdWRPCQAPWZmhYuHn70LwyeQFOzvC4C/cDfB75ZL85h6Pt3cEyiW5B7gzf+T9ABRGM+SHHFIZQ3p8isz4TajyYLkkQ3s1JuwRgXJJ9gGfT49NkSpeQzYvpDMKxic/kkLUp3D9MaxUbhL4u70YsyN75YH7xcmN5q75FUQCCqNp0BaYFraE5IdTiJUmf+ROVLm3XJLb9mjcN06gXJJ3Avdk9/0iqDK0L4slAZgA1Af1yeaFVMaQGXsrqeKkGOWBckn2xHhvqJNySQQ4YWVHxRk8RCYvZHICptX1ACYmURhLo8Zj4OfegyXyy8C9bzoB4GPArZmxKYg8hvbn2lYP2sCDLgnHMQwMOtjZETLjN2IMnyuXZORNI1AuyU3A553CJOJkKY5ncZxog9W7V/XBtOKExid78Fbs9MCYUf71TSHQBv8tSeVzTnESJxVSnMgkgLfWwyhBxCJgcF8WEPJxKP1WuSSfLZdk8PUS2FUpUS7JAeCdwG3AMeBGsbOkhg5jpXNM3nQd6YEsiAU48VVsoH0VC7Db323WrtRorHr4Ky/TWnwejVot4HvAD4BngOemjmvrdRMol+Qg8OvAbwC/ChwGASuFlcphpQqIk0XEZuL6/QzuH2kD7AVub6lXtVi6sELgGRAhbMwTVGeJGgsYvwrgA6eAp4iLwf+dOq7+tgTKJbkF+Ggb9E3xbQssG7EcxE6D2IjYWKkU+fFBhg+MkhvO94DcgUj7atSisdKkWfXbRGzEstEoIGouE7qLhLU5ouYSQAP4IfBd4OGp41rtEnjpc+SBh4EPbe0kC7EtcsMFsoMFBkYK5EeKWI5DMiz6hctu9IGvePWAoBkReBFhAGK125mAsD5PULtEsHYOjVoV4ONTx/XrHQL/DPzRBrdYwsDwAPmRPAOjA2QLWcSSra2btHI//WsIL8TGRELLDWm5AX4jJAxBxAYUb/4U3sIpBd4+dVyfc4APdIAXxguMXjtKbjiHZW01QUWg7Y9YoB0gUft7r5UT+k7bThttA07q220tyyZXsMgVB0AswgBa9YD6VZfMvrfhLZwS4H3Acw5wBbgOIFvMkh/Nbx1JG8SAGiBMAEiAwtqlPtqsJ6mPDeA4NtagTW2xhb86DcBilaWp+ElOdGAtX1ymsdLYBYFer/QuWtuvBxv0Znf6lZkrBG4Fb/5ZWgGLv/sgMyIybgHfaIWcBEDh8guX8Ztbzli7INIuHbpg2wWdWS8pNul7wXafXddXrizjXl2jefkkGrp8v8wDl1fxgWtFVbnvDvmlj93B4xmHcYBMPsPhWw9jpTKQmoD0frAH25177U8LWpdiXV/pJKnD5tlod/pGxWPp3ApB5SJhbZZXF/jWXV/gi+0BPAfgwac4m0nxl39wO/9oWU4qdeiDVPQwwweuRTqdqqGbxGisswvgnoX6s+DP9/FKlIjpZPJaINH66r2FvtWIWHplkdBdIazNslLn2d/+Ig8mBlhxAFTVFZHv33CQv3/3DeHxsDKNHjpKo+pQGMkCJgathvUENhDWYw9N3A2RC9WT4L60BZH2MxomQDqsz1JRj/UjAs9w5cV5Qq9BUJnGC7jyx1/jr5o+Zt06TCfnyvP3/RuPlOf4qjf/f9TPPUqr5uKu1duDR20gHasaEI0BRY04pIbfCxO/E3tmS9GYxIaE3Vy1Rn6TuTMXCRprBJUZwkgbJ57gk6emqSY6e0VV/S4BVQ2BMx8+wZcurfBfjYtP0Jw7ibtap9VorpPoeiFK6NqhEC6DpODgH0L+5j4kkuGVTPo4adW0mDtzkVa1QtiYx6gJvvljPvnlp7mQePiSqi5ATzmtqi5Q/tAJ/na5xo9rZx8hqFygtrhGFAY9edDxSoIIGs8e3kzbGx8Ba6d1xazPSuqzfH6OxtU1Im8VNZF54gx//Tff4aeJB1aB850fm5ZbVb1a9zh3z5f4tOub2erZb2OCJtWFVbTXA0SgGv/uxnk7X7yLYGXhmvtg6NdAMjsQUZprNZbPz6GBCxpx5jJf/rNv8INEIxd4SRMl9JYbGlWdvrDMxa88zWej5mrUuPgkgesTND02JHHXI4ZNeYKBYAXcn8XhdOhPYfg9YOX6Urg6c7VrkEqTFz/6L3w9cdsHXmiHele225G9/MCTPLNc5ydB7TKqESYMEiAT4DeFVfJ+CM2XYyK5G2DyT+LwckY3DRj669gef4Gv9Mw4L6iq1/uM0w+9qqqIvCTgYhIzT8f6vSFDMg96pls1ENXiKVYccEZg7FgcVurH9/wl0qOD+P4FIneReovOQbACL6pufbq97Z5YVaPhAdZQAyaKc6BfyCRnpU0eSuiNB/5lcF+BxumYlHcJALt4mIHrbgfAGDpxPqOqq/0w9vVAt4HNsDgZVCNEeuM/4YEu4J6k3tCm10OJtlEL9eYIKi4gjBd1kHgnNr0tvp0IAAcklQcTYdusg9guZDbptgK/+b6dEtTEZcdYIRwFmrrDqcOuCFhODtUIy5adwe8EuGv5iI3eM9iOgEaIZTM0EI62B9tWtiVQLkkKGBUnBxph2dtZOxE+m0gpuwkv26HtAYtChlF2cW61kwdGABE7jYhB+gF+LR7YcN9sIG070ClLBjKMA+k3SqAI8QmBSD/rbgTsey2aqw3ctRqRH5DOp8gP58gW03EObUkq7ltsUDWIWKQsBvaMAGLH1t9g1XUPGBPhrtSoLVUJ3BaqBtNaw/gNAncI92oVMORHcwzuK5IZcLYIL4NldQwBjr2HBERsxNq8iEVBQG2pSn25hgkCTFAnqM4QuUto1DkZFKx0ATs3Ri0coba4RibvMLS/SH4kF3u23a9lxTmgqjgWOUBExOktH14LgWx86cR63LlXaVBfqeFVXTSKCN0Fgso0xlvBGPzFKj86fYmny3PM3DGltx3ZV7u94NfeQnUWOzuK8YfxKnVsRyiO5ylMDJDJOSCdRdEQbwUBsIHXTWAJwAQNAs9nZfYqzaqL8QNM6BJUZwmrs2jk4Ycsn73Cv3/he3znmXOsdTp46CleBb7658e45f03R3fvG1w6Ks0lS1J5onSRVa/Jyqwhk0/hZKx4xTchrYCVBIG+shOBl4FaUJku2rkx6o0KUXM5PohtLqOqutrg1LMXefTTj/DDNbe/pe5/lNP3P8rpY29j/73v5oNHJhrvywSNiQgLK53HCzLxwa+JX46sNji3G4w7Hq+XS/IJVf5BBOnoWgGL5xZ57OEf8dh/PMdWu/mO+MQbkByw4R1ALo31qbt4x7uu5wMTg7zLknWgfsjyp77Nx//zeRaA09vVQrt6P/CZD8vv33CQo65P9acznP2n/+ZnftgtdXtFgWVgTlW7oSQiGeL/TlxDz+xy6xEGP/IObj48zlu8APeBJ3niJ+eptG8/r6oV+siuCIjIjRCfGW0jIfEx5WXV/i8nRESACWCSHq/0kZPb9bebWghgkf4E1oB5YElV+3mlK+3ibBFYFJECsVf298Gyth142KUHAERkEjgEpIA6cWwvbLVLeq0iIhYwRuyZQeKZZxV4VXXboz/+HwP+91Qbpr+OAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
padding-left: 64px;
padding-top: 0.2em;
margin-bottom: 00px;
height: 60px;
border-bottom: 3px solid #666;
}
h2 {
margin-top: 1.5em;
}
pre {
padding: 1em;
background-color: #EEE;
border: 1px solid #BBB;
width: auto;
}
tt, code {
background-color: #DDD;
padding: 0.2ex;
}

View file

@ -0,0 +1,109 @@
<?php
/**
* SemanticScuttle - your social bookmark manager.
*
* PHP version 5.
*
* @category Bookmarking
* @package SemanticScuttle
* @author Christian Weiske <cweiske@cweiske.de>
* @license AGPL http://www.gnu.org/licenses/agpl.html
* @link http://sourceforge.net/projects/semanticscuttle
*/
/**
* Configuration handling
*
* @category Bookmarking
* @package SemanticScuttle
* @author Christian Weiske <cweiske@cweiske.de>
* @license AGPL http://www.gnu.org/licenses/agpl.html
* @link http://sourceforge.net/projects/semanticscuttle
*/
class SemanticScuttle_Config
{
/**
* Prefix for configuration files.
* Used to inject stream wrapper protocol for unit testing
*
* @var string
*/
public $filePrefix = '';
/**
* Finds the correct data directory
*
* @return string Full path to the data directory with a trailing slash
*/
protected function getDataDir()
{
if ('@data_dir@' == '@' . 'data_dir@') {
//non pear-install
$datadir = dirname(__FILE__) . '/../../data/';
} else {
//pear installation; files are in include path
$datadir = '@data_dir@/SemanticScuttle/';
}
return $datadir;
}
/**
* Tries to find a configuration file by looking in different
* places:
* - pear data_dir/SemanticScuttle/config-$hostname.php
* - pear data_dir/SemanticScuttle/config.php
* - /etc/semanticscuttle/config-$hostname.php
* - /etc/semanticscuttle/config.php
*
* Paths with host name have priority.
*
* @return array Array with config file path as first value
* and default config file path as second value.
* Any may be NULL if not found
*/
public function findFiles()
{
//use basename to prevent path injection
$host = basename($_SERVER['HTTP_HOST']);
$datadir = $this->getDataDir();
$arFiles = array(
$datadir . 'config.' . $host . '.php',
'/etc/semanticscuttle/config.' . $host . '.php',
$datadir . 'config.php',
'/etc/semanticscuttle/config.php',
);
$configfile = null;
foreach ($arFiles as $file) {
if (file_exists($this->filePrefix . $file)) {
$configfile = $file;
break;
}
}
//find default file
$arDefaultFiles = array_unique(
array(
substr($configfile, 0, -3) . 'default.php',
$datadir . 'config.default.php',
'/etc/semanticscuttle/config.default.php',
)
);
$defaultfile = null;
foreach ($arDefaultFiles as $file) {
if (file_exists($this->filePrefix . $file)) {
$defaultfile = $file;
break;
}
}
return array($configfile, $defaultfile);
}
}
?>

View file

@ -0,0 +1,52 @@
<?php
/**
* SemanticScuttle - your social bookmark manager.
*
* PHP version 5.
*
* @category Bookmarking
* @package SemanticScuttle
* @author Christian Weiske <cweiske@cweiske.de>
* @license AGPL http://www.gnu.org/licenses/agpl.html
* @link http://sourceforge.net/projects/semanticscuttle
*/
/**
* Server environment handling methods
*
* @category Bookmarking
* @package SemanticScuttle
* @author Christian Weiske <cweiske@cweiske.de>
* @license AGPL http://www.gnu.org/licenses/agpl.html
* @link http://sourceforge.net/projects/semanticscuttle
*/
class SemanticScuttle_Environment
{
/**
* Determines the correct $_SERVER['PATH_INFO'] value
*
* @return string New value
*/
public static function getServerPathInfo()
{
/* old code that does not work today.
if you find that this code helps you, tell us
and send us the output of var_export($_SERVER);
// Correct bugs with PATH_INFO (maybe for Apache 1 or CGI) -- for 1&1 host...
if (isset($_SERVER['PATH_INFO']) && isset($_SERVER['ORIG_PATH_INFO'])) {
if (strlen($_SERVER["PATH_INFO"])<strlen($_SERVER["ORIG_PATH_INFO"])) {
$_SERVER["PATH_INFO"] = $_SERVER["ORIG_PATH_INFO"];
}
if (strcasecmp($_SERVER["PATH_INFO"], $_SERVER["SCRIPT_NAME"]) == 0) {
unset($_SERVER["PATH_INFO"]);
}
if (strpos($_SERVER["PATH_INFO"], '.php') !== false) {
unset($_SERVER["PATH_INFO"]);
}
}
*/
return $_SERVER['PATH_INFO'];
}
}
?>

View file

@ -23,6 +23,23 @@
*/ */
class SemanticScuttle_Model_Bookmark class SemanticScuttle_Model_Bookmark
{ {
/**
* Status "public" / visible for all
*/
const SPUBLIC = 0;
/**
* Status "shared" / visible for people on your watchlist
*/
const SWATCHLIST = 1;
/**
* Status "private" / visible for yourself only
*/
const SPRIVATE = 2;
/** /**
* Checks if the given URL is valid and may be used with this * Checks if the given URL is valid and may be used with this
* SemanticScuttle installation. * SemanticScuttle installation.

View file

@ -35,6 +35,7 @@ class SemanticScuttle_Model_User
var $content; var $content;
var $datetime; var $datetime;
var $isAdmin; var $isAdmin;
var $privateKey;
/** /**
* Create a new user object * Create a new user object
@ -68,6 +69,29 @@ class SemanticScuttle_Model_User
return $this->username; return $this->username;
} }
/**
* Returns private key
*
* @param boolean return sanitized value which basically drops
* leading dash if exists
*
* @return string private key
*/
public function getPrivateKey($sanitized = false)
{
// Look for value only if not already set
if (!isset($this->privateKey)) {
$us = SemanticScuttle_Service_Factory::get('User');
$user = $us->getUser($this->id);
$this->privateKey = $user['privateKey'];
}
if ($sanitized == true) {
return substr($this->privateKey, -32);
} else {
return $this->privateKey;
}
}
/** /**
* Returns full user name as specified in the profile. * Returns full user name as specified in the profile.
* *

View file

@ -427,6 +427,7 @@ class SemanticScuttle_Service_Bookmark extends SemanticScuttle_DbService
$existence[$hashes[$row['bHash']]] = $row['count'] > 0; $existence[$hashes[$row['bHash']]] = $row['count'] > 0;
} }
$this->db->sql_freeresult($dbresult);
return $existence; return $existence;
} }

View file

@ -99,7 +99,7 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
$tags_count = is_array($tags)?count($tags):0; $tags_count = is_array($tags)?count($tags):0;
for ($i = 0; $i < $tags_count; $i++) { for ($i = 0; $i < $tags_count; $i++) {
$tags[$i] = trim(strtolower($tags[$i])); $tags[$i] = trim(utf8_strtolower($tags[$i]));
if ($fromApi) { if ($fromApi) {
include_once 'SemanticScuttle/functions.php'; include_once 'SemanticScuttle/functions.php';
$tags[$i] = convertTag($tags[$i], 'in'); $tags[$i] = convertTag($tags[$i], 'in');
@ -584,7 +584,7 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
if (is_int($days)) { if (is_int($days)) {
$query .= ' AND B.bDatetime > "' $query .= ' AND B.bDatetime > "'
. date('Y-m-d H:i:s', time() - (86400 * $days)) . gmdate('Y-m-d H:i:s', time() - (86400 * $days))
. '"'; . '"';
} }

View file

@ -141,10 +141,10 @@ class SemanticScuttle_Service_Tag extends SemanticScuttle_DbService
//normalize //normalize
if(!is_array($tags)) { if(!is_array($tags)) {
$tags = strtolower(trim($tags)); $tags = utf8_strtolower(trim($tags));
} else { } else {
for($i=0; $i<count($tags); $i++) { for($i=0; $i<count($tags); $i++) {
$tags[$i] = strtolower(trim($tags[$i])); $tags[$i] = utf8_strtolower(trim($tags[$i]));
} }
} }
return $tags; return $tags;

View file

@ -50,7 +50,8 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
protected $fields = array( protected $fields = array(
'primary' => 'uId', 'primary' => 'uId',
'username' => 'username', 'username' => 'username',
'password' => 'password' 'password' => 'password',
'privateKey' => 'privateKey'
); );
protected $profileurl; protected $profileurl;
@ -215,6 +216,18 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
return $this->_getuser($this->getFieldName('username'), $username); return $this->_getuser($this->getFieldName('username'), $username);
} }
/**
* Returns user row from database.
*
* @param string $privateKey Private Key
*
* @return array User array from database, false if no user was found
*/
public function getUserByPrivateKey($privateKey)
{
return $this->_getuser($this->getFieldName('privateKey'), $privateKey);
}
function getObjectUserByUsername($username) { function getObjectUserByUsername($username) {
$user = $this->_getuser($this->getFieldName('username'), $username); $user = $this->_getuser($this->getFieldName('username'), $username);
if($user != false) { if($user != false) {
@ -279,6 +292,22 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
return ($this->getCurrentUserId() !== false); return ($this->getCurrentUserId() !== false);
} }
/**
* Tells you if the private key is enabled and valid
*
* @param string $privateKey Private Key
*
* @return boolean True if enabled and valid
*/
public function isPrivateKeyValid($privateKey)
{
// check length of private key
if (strlen($privateKey) == 32) {
return true;
}
return false;
}
/** /**
* Returns the current user object * Returns the current user object
* *
@ -293,7 +322,7 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
{ {
if (!is_null($newval)) { if (!is_null($newval)) {
//internal use only: reset currentuser //internal use only: reset currentuser
$currentuser = $newval; $this->currentuser = $newval;
} else if ($refresh || !isset($this->currentuser)) { } else if ($refresh || !isset($this->currentuser)) {
if ($id = $this->getCurrentUserId()) { if ($id = $this->getCurrentUserId()) {
$this->currentuser = $this->getUser($id); $this->currentuser = $this->getUser($id);
@ -509,6 +538,46 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
} }
} }
/**
* Try to authenticate via the privateKey
*
* @param string $privateKey Private Key
*
* @return boolean true if the user could be authenticated,
* false if not.
*/
public function loginPrivateKey($privateKey)
{
/* Check if private key valid and enabled */
if (!$this->isPrivateKeyValid($privateKey)) {
return false;
}
$query = 'SELECT '. $this->getFieldName('primary') .' FROM '
. $this->getTableName() .' WHERE '
. $this->getFieldName('privateKey') .' = "'
. $this->db->sql_escape($privateKey) .'"';
if (!($dbresult = $this->db->sql_query($query))) {
message_die(
GENERAL_ERROR,
'Could not get user',
'', __LINE__, __FILE__, $query, $this->db
);
return false;
}
$row = $this->db->sql_fetchrow($dbresult);
$this->db->sql_freeresult($dbresult);
if ($row) {
$this->setCurrentUserId($row[$this->getFieldName('primary')], false);
return true;
} else {
return false;
}
}
/** /**
* Logs the user off * Logs the user off
* *
@ -519,7 +588,8 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
@setcookie($this->getCookiekey(), '', time() - 1, '/'); @setcookie($this->getCookiekey(), '', time() - 1, '/');
unset($_COOKIE[$this->getCookiekey()]); unset($_COOKIE[$this->getCookiekey()]);
session_unset(); session_unset();
$this->getCurrentUser(TRUE, false); $this->currentuserId = null;
$this->currentuser = null;
} }
function getWatchlist($uId) { function getWatchlist($uId) {
@ -603,11 +673,12 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
return false; return false;
} }
$arrWatch = array(); $retval = true;
if ($this->db->sql_numrows($dbresult) == 0) if ($this->db->sql_numrows($dbresult) == 0)
return false; $retval = false;
else
return true; $this->db->sql_freeresult($dbresult);
return $retval;
} }
function setWatchStatus($subjectUserID) { function setWatchStatus($subjectUserID) {
@ -649,11 +720,12 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
* @param string $username Username to use * @param string $username Username to use
* @param string $password Password to use * @param string $password Password to use
* @param string $email Email to use * @param string $email Email to use
* @param string $privateKey Key for RSS auth
* *
* @return mixed Integer user ID if all is well, * @return mixed Integer user ID if all is well,
* boolean false if an error occured * boolean false if an error occured
*/ */
public function addUser($username, $password, $email) public function addUser($username, $password, $email, $privateKey = null)
{ {
// Set up the SQL UPDATE statement. // Set up the SQL UPDATE statement.
$datetime = gmdate('Y-m-d H:i:s', time()); $datetime = gmdate('Y-m-d H:i:s', time());
@ -663,7 +735,8 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
'password' => $password, 'password' => $password,
'email' => $email, 'email' => $email,
'uDatetime' => $datetime, 'uDatetime' => $datetime,
'uModified' => $datetime 'uModified' => $datetime,
'privateKey' => $privateKey
); );
$sql = 'INSERT INTO '. $this->getTableName() $sql = 'INSERT INTO '. $this->getTableName()
. ' '. $this->db->sql_build_array('INSERT', $values); . ' '. $this->db->sql_build_array('INSERT', $values);
@ -693,16 +766,38 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
* @param string $email Email to use * @param string $email Email to use
* @param string $homepage User's homepage * @param string $homepage User's homepage
* @param string $uContent User note * @param string $uContent User note
* @param string $privateKey RSS Private Key
* @param boolean $enablePrivateKey RSS Private Key Flag
* *
* @return boolean True when all is well, false if not * @return boolean True when all is well, false if not
*/ */
public function updateUser( public function updateUser(
$uId, $password, $name, $email, $homepage, $uContent $uId, $password, $name, $email, $homepage, $uContent,
$privateKey = null, $enablePrivateKey = false
) { ) {
if (!is_numeric($uId)) { if (!is_numeric($uId)) {
return false; return false;
} }
// prepend '-' to privateKey if disabled
if ($privateKey != null && strlen($privateKey) == 32
&& $enablePrivateKey == false
) {
$privateKey = '-' . $privateKey;
}
// remove '-' from privateKey if enabling
if ($privateKey != null && strlen($privateKey) == 33
&& $enablePrivateKey == true
) {
$privateKey = substr($privateKey, 1, 32);
}
// if new user is enabling Private Key, create new key
if ($privateKey == null && $enablePrivateKey == true) {
$privateKey = $this->getNewPrivateKey();
}
// Set up the SQL UPDATE statement. // Set up the SQL UPDATE statement.
$moddatetime = gmdate('Y-m-d H:i:s', time()); $moddatetime = gmdate('Y-m-d H:i:s', time());
if ($password == '') { if ($password == '') {
@ -711,7 +806,8 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
'name' => $name, 'name' => $name,
'email' => $email, 'email' => $email,
'homepage' => $homepage, 'homepage' => $homepage,
'uContent' => $uContent 'uContent' => $uContent,
'privateKey' => $privateKey
); );
} else { } else {
$updates = array( $updates = array(
@ -720,7 +816,8 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
'name' => $name, 'name' => $name,
'email' => $email, 'email' => $email,
'homepage' => $homepage, 'homepage' => $homepage,
'uContent' => $uContent 'uContent' => $uContent,
'privateKey' => $privateKey
); );
} }
$sql = 'UPDATE '. $this->getTableName() $sql = 'UPDATE '. $this->getTableName()
@ -837,6 +934,56 @@ class SemanticScuttle_Service_User extends SemanticScuttle_DbService
} }
} }
/**
* Generates a new private key and confirms it isn't being used.
* Private key is 32 characters long, consisting of lowercase and
* numeric characters.
*
* @return string the new key value
*/
public function getNewPrivateKey()
{
do {
$newKey = md5(uniqid('SemanticScuttle', true));
} while ($this->privateKeyExists($newKey));
return $newKey;
}
/**
* Checks if a private key already exists
*
* @param string $privateKey key that has been generated
*
* @return boolean true when the private key exists,
* False if not.
*/
public function privateKeyExists($privateKey)
{
if (!$privateKey) {
return false;
}
$crit = array('privateKey' => $privateKey);
$sql = 'SELECT COUNT(*) as "0" FROM '
. $GLOBALS['tableprefix'] . 'users'
. ' WHERE '. $this->db->sql_build_array('SELECT', $crit);
if (!($dbresult = $this->db->sql_query($sql))) {
message_die(
GENERAL_ERROR, 'Could not get vars', '',
__LINE__, __FILE__, $sql, $this->db
);
}
if ($this->db->sql_fetchfield(0, 0) > 0) {
$exists = true;
} else {
$exists = false;
}
$this->db->sql_freeresult($dbresult);
return $exists;
}
function isReserved($username) { function isReserved($username) {
if (in_array($username, $GLOBALS['reservedusers'])) { if (in_array($username, $GLOBALS['reservedusers'])) {
return true; return true;

View file

@ -69,16 +69,6 @@ define('PAGE_WATCHLIST', "watchlist");
// installations on the same host server // installations on the same host server
define('INSTALLATION_ID', md5($GLOBALS['dbname'].$GLOBALS['tableprefix'])); define('INSTALLATION_ID', md5($GLOBALS['dbname'].$GLOBALS['tableprefix']));
// Correct bugs with PATH_INFO (maybe for Apache 1 or CGI) -- for 1&1 host... //currently not needed
if (isset($_SERVER['PATH_INFO']) && isset($_SERVER['ORIG_PATH_INFO'])) { //$_SERVER['PATH_INFO'] = SemanticScuttle_Environment::getServerPathInfo();
if (strlen($_SERVER["PATH_INFO"])<strlen($_SERVER["ORIG_PATH_INFO"])) {
$_SERVER["PATH_INFO"] = $_SERVER["ORIG_PATH_INFO"];
}
if (strcasecmp($_SERVER["PATH_INFO"], $_SERVER["SCRIPT_NAME "]) == 0) {
unset($_SERVER["PATH_INFO"]);
}
if (strpos($_SERVER["PATH_INFO"], '.php') !== false) {
unset($_SERVER["PATH_INFO"]);
}
}
?> ?>

View file

@ -25,8 +25,20 @@ if ('@data_dir@' == '@' . 'data_dir@') {
//FIXME: when you have multiple installations, the www_dir will be wrong //FIXME: when you have multiple installations, the www_dir will be wrong
$wwwdir = '@www_dir@/SemanticScuttle/'; $wwwdir = '@www_dir@/SemanticScuttle/';
} }
require_once dirname(__FILE__) . '/Environment.php';
require_once dirname(__FILE__) . '/Config.php';
if (!file_exists($datadir . '/config.php')) { $cfg = new SemanticScuttle_Config();
list($configfile, $defaultfile) = $cfg->findFiles();
if ($defaultfile === null) {
header('HTTP/1.0 500 Internal Server Error');
die(
'No default configuration file config.default.php found.'
. ' This is really, really strange'
. "\n"
);
}
if ($configfile === null) {
header('HTTP/1.0 500 Internal Server Error'); header('HTTP/1.0 500 Internal Server Error');
die( die(
'Please copy "config.php.dist" to "config.php" in data/ folder.' 'Please copy "config.php.dist" to "config.php" in data/ folder.'
@ -39,8 +51,8 @@ set_include_path(
); );
// 1 // First requirements part (before debug management) // 1 // First requirements part (before debug management)
require_once $datadir . '/config.default.php'; require_once $defaultfile;
require_once $datadir . '/config.php'; require_once $configfile;
if (isset($_GET['unittestMode']) && $_GET['unittestMode'] == 1 if (isset($_GET['unittestMode']) && $_GET['unittestMode'] == 1
) { ) {

View file

@ -46,6 +46,7 @@ class AllTests extends PHPUnit_Framework_TestSuite
$suite->addTestFile($tdir . '/VoteTest.php'); $suite->addTestFile($tdir . '/VoteTest.php');
$suite->addTestFile($tdir . '/UserTest.php'); $suite->addTestFile($tdir . '/UserTest.php');
$suite->addTestFile($tdir . '/Api/ExportCsvTest.php'); $suite->addTestFile($tdir . '/Api/ExportCsvTest.php');
$suite->addTestFile($tdir . '/Api/OpenSearchTest.php');
$suite->addTestFile($tdir . '/Api/PostsAddTest.php'); $suite->addTestFile($tdir . '/Api/PostsAddTest.php');
$suite->addTestFile($tdir . '/Api/PostsDeleteTest.php'); $suite->addTestFile($tdir . '/Api/PostsDeleteTest.php');
$suite->addTestFile($tdir . '/Api/PostsUpdateTest.php'); $suite->addTestFile($tdir . '/Api/PostsUpdateTest.php');

View file

@ -108,7 +108,7 @@ TXT;
$this->assertEquals($bmUrl, $bm['bAddress']); $this->assertEquals($bmUrl, $bm['bAddress']);
$this->assertEquals($bmTitle, $bm['bTitle']); $this->assertEquals($bmTitle, $bm['bTitle']);
$this->assertEquals($bmDescription, $bm['bDescription']); $this->assertEquals($bmDescription, stripslashes($bm['bDescription']));
$this->assertEquals($bmTags, $bm['tags']); $this->assertEquals($bmTags, $bm['tags']);
$this->assertEquals( $this->assertEquals(
gmdate('Y-m-d H:i:s', strtotime($bmDatetime)), gmdate('Y-m-d H:i:s', strtotime($bmDatetime)),
@ -170,7 +170,7 @@ TXT;
$this->assertEquals($bmUrl, $bm['bAddress']); $this->assertEquals($bmUrl, $bm['bAddress']);
$this->assertEquals($bmTitle, $bm['bTitle']); $this->assertEquals($bmTitle, $bm['bTitle']);
$this->assertEquals($bmDescription, $bm['bDescription']); $this->assertEquals($bmDescription, stripslashes($bm['bDescription']));
$this->assertEquals($bmTags, $bm['tags']); $this->assertEquals($bmTags, $bm['tags']);
$this->assertEquals( $this->assertEquals(
gmdate('Y-m-d H:i:s', strtotime($bmDatetime)), gmdate('Y-m-d H:i:s', strtotime($bmDatetime)),

View file

@ -282,10 +282,10 @@ class Bookmark2TagTest extends TestBase
public function testGetPopularTagsDays() public function testGetPopularTagsDays()
{ {
$user = $this->addUser(); $user = $this->addUser();
$this->addTagBookmark($user, array('one', 'two'), 'today'); $this->addTagBookmark($user, array('one', 'two'), 'now');
$this->addTagBookmark($user, array('one', 'thr'), 'today'); $this->addTagBookmark($user, array('one', 'thr'), 'now');
$this->addTagBookmark($user, array('one', 'two'), '-1 day 1 hour'); $this->addTagBookmark($user, array('one', 'two'), '-1 day -1 hour');
$this->addTagBookmark($user, array('one', 'thr'), '-3 days 1 hour'); $this->addTagBookmark($user, array('one', 'thr'), '-3 days -1 hour');
$arTags = $this->b2ts->getPopularTags(null, 10, null, 1); $arTags = $this->b2ts->getPopularTags(null, 10, null, 1);
$this->assertInternalType('array', $arTags); $this->assertInternalType('array', $arTags);

View file

@ -1330,5 +1330,51 @@ class BookmarkTest extends TestBase
/**
* Test private bookmarks
*
* @return void
*/
public function testPrivateBookmarks()
{
$uid = $this->addUser();
/* create private bookmark */
$this->bs->addBookmark(
'http://test', 'test', 'desc', 'note',
2,//private
array(), null, null, false, false, $uid
);
/* create public bookmark */
$this->bs->addBookmark(
'http://example.org', 'title', 'desc', 'priv',
0,//public
array(), null, null, false, false, $uid
);
$this->assertEquals(1, $this->bs->countBookmarks($uid, 'public'));
$this->assertEquals(1, $this->bs->countBookmarks($uid, 'private'));
$this->assertEquals(0, $this->bs->countBookmarks($uid, 'shared'));
$this->assertEquals(2, $this->bs->countBookmarks($uid, 'all'));
$this->us->setCurrentUserId($uid);
$bookmarks = $this->bs->getBookmarks();
// first record should be private bookmark
$b0 = $bookmarks['bookmarks'][0];
$this->assertEquals('test', $b0['bTitle']);
// second record should be public bookmark
$b0 = $bookmarks['bookmarks'][1];
$this->assertEquals('title', $b0['bTitle']);
// test non authenticated query
$this->us->setCurrentUserId(null);
$bookmarks = $this->bs->getBookmarks();
// should only result in one link - public
$b2 = $bookmarks['bookmarks'][0];
$this->assertEquals('title', $b2['bTitle']);
// there should be no second record
$this->assertEquals(1,count($bookmarks['bookmarks']));
}
} }
?> ?>

View file

@ -0,0 +1,206 @@
<?php
//that's PEAR's Stream_Var package
require_once 'Stream/Var.php';
class SemanticScuttle_ConfigTest_StreamVar extends Stream_Var {
public function url_stat($path, $flags)
{
$url = parse_url($path);
$scope = $url['host'];
if (isset($url['path'])) {
$varpath = substr($url['path'], 1);
} else {
$varpath = '';
}
if (!$this->_setPointer($scope, $varpath)) {
return false;
}
return parent::url_stat($path, $flags);
}
}
class SemanticScuttle_ConfigTest extends PHPUnit_Framework_TestCase
{
/**
* Configuration object to test
*/
protected $cfg;
public function setUpWrapper()
{
if (!in_array('unittest', stream_get_wrappers())) {
stream_wrapper_register(
'unittest', 'SemanticScuttle_ConfigTest_StreamVar'
);
}
$this->cfg = $this->getMock(
'SemanticScuttle_Config',
array('getDataDir')
);
$this->cfg->expects($this->once())
->method('getDataDir')
->will($this->returnValue('/data-dir/'));
$this->cfg->filePrefix = 'unittest://GLOBALS/unittest-dir';
}
public function testFindLocalData()
{
$this->setUpWrapper();
$GLOBALS['unittest-dir']['data-dir'] = array(
'config.php' => 'content',
'config.default.php' => 'content'
);
$this->assertEquals(
array(
'/data-dir/config.php',
'/data-dir/config.default.php'
),
$this->cfg->findFiles()
);
}
public function testFindHostPreferredOverNonHostConfig()
{
$this->setUpWrapper();
$_SERVER['HTTP_HOST'] = 'foo.example.org';
$GLOBALS['unittest-dir']['data-dir'] = array(
'config.php' => 'content',
'config.foo.example.org.php' => 'content',
'config.default.php' => 'content'
);
$this->assertEquals(
array(
'/data-dir/config.foo.example.org.php',
'/data-dir/config.default.php'
),
$this->cfg->findFiles()
);
}
public function testFindEtcHostPreferredOverLocalConfigPhp()
{
$this->setUpWrapper();
$_SERVER['HTTP_HOST'] = 'foo.example.org';
$GLOBALS['unittest-dir'] = array(
'etc' => array(
'semanticscuttle' => array(
'config.foo.example.org.php' => 'content',
)
),
'data-dir' => array(
'config.php' => 'content',
'config.default.php' => 'content'
)
);
$this->assertEquals(
array(
'/etc/semanticscuttle/config.foo.example.org.php',
'/data-dir/config.default.php'
),
$this->cfg->findFiles()
);
}
public function testFindEtcConfig()
{
$this->setUpWrapper();
$GLOBALS['unittest-dir'] = array(
'etc' => array(
'semanticscuttle' => array(
'config.php' => 'content'
)
),
'data-dir' => array(
'config.default.php' => 'content'
)
);
$this->assertEquals(
array(
'/etc/semanticscuttle/config.php',
'/data-dir/config.default.php'
),
$this->cfg->findFiles()
);
}
public function testFindEtcDefaultConfig()
{
$this->setUpWrapper();
$GLOBALS['unittest-dir'] = array(
'etc' => array(
'semanticscuttle' => array(
'config.php' => 'content',
'config.default.php' => 'content'
)
),
);
$this->assertEquals(
array(
'/etc/semanticscuttle/config.php',
'/etc/semanticscuttle/config.default.php'
),
$this->cfg->findFiles()
);
}
public function testFindLocalDefaultPreferredOverEtcDefault()
{
$this->setUpWrapper();
$GLOBALS['unittest-dir'] = array(
'etc' => array(
'semanticscuttle' => array(
'config.php' => 'content',
'config.default.php' => 'content'
)
),
'data-dir' => array(
'config.php' => 'content',
'config.default.php' => 'content'
)
);
$this->assertEquals(
array(
'/data-dir/config.php',
'/data-dir/config.default.php'
),
$this->cfg->findFiles()
);
}
public function testFindSameDirDefaultPreferred()
{
$this->setUpWrapper();
$GLOBALS['unittest-dir'] = array(
'etc' => array(
'semanticscuttle' => array(
'config.php' => 'content',
'config.default.php' => 'content'
)
),
'data-dir' => array(
'config.default.php' => 'content'
)
);
$this->assertEquals(
array(
'/etc/semanticscuttle/config.php',
'/etc/semanticscuttle/config.default.php'
),
$this->cfg->findFiles()
);
}
}
?>

View file

@ -0,0 +1,95 @@
<?php
class SemanticScuttle_EnvironmentTest extends PHPUnit_Framework_TestCase
{
public function testServerPathInfoModPhp()
{
$_SERVER = array(
'HTTP_USER_AGENT' => 'Opera/9.80 (X11; Linux x86_64; U; de) Presto/2.9.168 Version/11.50',
'HTTP_HOST' => 'bm-cgi.bogo',
'HTTP_ACCEPT' => 'text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1',
'HTTP_ACCEPT_LANGUAGE' => 'de-DE,de;q=0.9,en;q=0.8',
'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
'HTTP_COOKIE' => 'PHPSESSID=ga446jhs0e09hkt60u9bsmp0n0',
'HTTP_CACHE_CONTROL' => 'no-cache',
'HTTP_CONNECTION' => 'Keep-Alive',
'PATH' => '/usr/local/bin:/usr/bin:/bin',
'SERVER_SIGNATURE' => '<address>Apache/2.2.17 (Ubuntu) Server at bm-cgi.bogo Port 80</address>',
'SERVER_SOFTWARE' => 'Apache/2.2.17 (Ubuntu)',
'SERVER_NAME' => 'bm-cgi.bogo',
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'REMOTE_ADDR' => '127.0.0.1',
'DOCUMENT_ROOT' => '/etc/apache2/htdocs',
'SERVER_ADMIN' => '[no address given]',
'SCRIPT_FILENAME' => '/home/cweiske/Dev/html/hosts/bm-cgi.bogo/profile.php',
'REMOTE_PORT' => '45349',
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'REQUEST_METHOD' => 'GET',
'QUERY_STRING' => '',
'REQUEST_URI' => '/profile.php/dummy',
'SCRIPT_NAME' => '/profile.php',
'PATH_INFO' => '/dummy',
'PATH_TRANSLATED' => '/home/cweiske/Dev/html/hosts/bm-cgi.bogo/dummy',
'PHP_SELF' => '/profile.php/dummy',
'REQUEST_TIME' => 1311422546,
);
$this->assertEquals(
'/dummy', SemanticScuttle_Environment::getServerPathInfo()
);
}
public function testServerPathInfoFastCgi()
{
$_SERVER = array(
'PHP_FCGI_MAX_REQUESTS' => '5000',
'PHPRC' => '/etc/php5/cgi/5.3.6/',
'PHP_FCGI_CHILDREN' => '3',
'PWD' => '/var/www/cgi-bin',
'FCGI_ROLE' => 'RESPONDER',
'REDIRECT_HANDLER' => 'php-cgi',
'REDIRECT_STATUS' => '200',
'HTTP_USER_AGENT' => 'Opera/9.80 (X11; Linux x86_64; U; de) Presto/2.9.168 Version/11.50',
'HTTP_HOST' => 'bm-cgi.bogo',
'HTTP_ACCEPT' => 'text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1',
'HTTP_ACCEPT_LANGUAGE' => 'de-DE,de;q=0.9,en;q=0.8',
'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
'HTTP_COOKIE' => 'PHPSESSID=ga446jhs0e09hkt60u9bsmp0n0',
'HTTP_CONNECTION' => 'Keep-Alive',
'PATH' => '/usr/local/bin:/usr/bin:/bin',
'SERVER_SIGNATURE' => '<address>Apache/2.2.17 (Ubuntu) Server at bm-cgi.bogo Port 80</address>',
'SERVER_SOFTWARE' => 'Apache/2.2.17 (Ubuntu)',
'SERVER_NAME' => 'bm-cgi.bogo',
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'REMOTE_ADDR' => '127.0.0.1',
'DOCUMENT_ROOT' => '/etc/apache2/htdocs',
'SERVER_ADMIN' => '[no address given]',
'SCRIPT_FILENAME' => '/home/cweiske/Dev/html/hosts/bm-cgi.bogo/profile.php',
'REMOTE_PORT' => '45342',
'REDIRECT_URL' => '/profile.php/dummy',
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'REQUEST_METHOD' => 'GET',
'QUERY_STRING' => '',
'REQUEST_URI' => '/profile.php/dummy',
'SCRIPT_NAME' => '/profile.php',
'PATH_INFO' => '/dummy',
'PATH_TRANSLATED' => '/etc/apache2/htdocs/dummy',
'ORIG_PATH_INFO' => '/profile.php/dummy',
'ORIG_SCRIPT_NAME' => '/cgi-bin-php/php-cgi-5.3.6',
'ORIG_SCRIPT_FILENAME' => '/var/www/cgi-bin/php-cgi-5.3.6',
'ORIG_PATH_TRANSLATED' => '/home/cweiske/Dev/html/hosts/bm-cgi.bogo/profile.php/dummy',
'PHP_SELF' => '/profile.php/dummy',
'REQUEST_TIME' => 1311422521,
);
$this->assertEquals(
'/dummy', SemanticScuttle_Environment::getServerPathInfo()
);
}
}
?>

View file

@ -332,6 +332,7 @@ class Tag2TagTest extends TestBase
$this->assertSame('B3', $results['bookmarks'][0]['bTitle']); $this->assertSame('B3', $results['bookmarks'][0]['bTitle']);
$results = $bs->getBookmarks(0, NULL, 1, 'aa+ee'); $results = $bs->getBookmarks(0, NULL, 1, 'aa+ee');
$this->assertSame(1, intval($results['total'])); $this->assertSame(1, intval($results['total']));
$this->assertSame('B2', $results['bookmarks'][0]['bTitle']); $this->assertSame('B2', $results['bookmarks'][0]['bTitle']);

View file

@ -76,16 +76,18 @@ class TestBase extends PHPUnit_Framework_TestCase
/** /**
* Creates a new user in the database. * Creates a new user in the database.
* *
* @param string $username Username * @param string $username Username, may be null
* @param string $password Password * @param string $password Password, may be null
* @param mixed $privateKey String private key or boolean true to generate one
* *
* @return integer ID of user * @return integer ID of user
* *
* @uses addUserData() * @uses addUserData()
*/ */
protected function addUser($username = null, $password = null) protected function addUser(
{ $username = null, $password = null, $privateKey = null
return reset($this->addUserData($username, $password)); ) {
return reset($this->addUserData($username, $password, $privateKey));
} }
@ -93,13 +95,15 @@ class TestBase extends PHPUnit_Framework_TestCase
/** /**
* Creates a new user in the database and returns id, username and password. * Creates a new user in the database and returns id, username and password.
* *
* @param string $username Username * @param string $username Username, may be null
* @param string $password Password * @param string $password Password, may be null
* @param mixed $privateKey String private key or boolean true to generate one
* *
* @return array ID of user, Name of user, password of user * @return array ID of user, Name of user, password of user, privateKey
*/ */
protected function addUserData($username = null, $password = null) protected function addUserData(
{ $username = null, $password = null, $privateKey = null
) {
$us = SemanticScuttle_Service_Factory::get('User'); $us = SemanticScuttle_Service_Factory::get('User');
$rand = rand(); $rand = rand();
@ -109,13 +113,17 @@ class TestBase extends PHPUnit_Framework_TestCase
if ($password === null) { if ($password === null) {
$password = $rand; $password = $rand;
} }
if ($privateKey === true) {
$privateKey = $this->us->getNewPrivateKey();
}
$uid = $us->addUser( $uid = $us->addUser(
$username, $username,
$password, $password,
'unittest-' . $rand . '@example.org' 'unittest-' . $rand . '@example.org',
$privateKey
); );
return array($uid, $username, $password); return array($uid, $username, $password, $privateKey);
} }

View file

@ -24,7 +24,20 @@ require_once 'HTTP/Request2.php';
*/ */
class TestBaseApi extends TestBase class TestBaseApi extends TestBase
{ {
/**
* Created from the configured host and the $urlPart.
* Should be used as base for all generated URLs
*
* @var string
*/
protected $url; protected $url;
/**
* Part of the URL behind the configured host.
* Needs to be overwritten in each derived test case class.
*
* @var string
*/
protected $urlPart = null; protected $urlPart = null;
/** /**
@ -167,20 +180,22 @@ class TestBaseApi extends TestBase
* @param string $urlSuffix Suffix for the URL * @param string $urlSuffix Suffix for the URL
* @param mixed $auth If user authentication is needed (true/false) * @param mixed $auth If user authentication is needed (true/false)
* or array with username and password * or array with username and password
* @param boolean $privateKey True if to add user with private key
* *
* @return array(HTTP_Request2, integer) HTTP request object and user id * @return array(HTTP_Request2, integer) HTTP request object and user id
* *
* @uses getRequest() * @uses getRequest()
*/ */
protected function getLoggedInRequest($urlSuffix = null, $auth = true) protected function getLoggedInRequest(
{ $urlSuffix = null, $auth = true, $privateKey = null
) {
if (is_array($auth)) { if (is_array($auth)) {
list($username, $password) = $auth; list($username, $password) = $auth;
} else { } else {
$username = 'testuser'; $username = 'testuser';
$password = 'testpassword'; $password = 'testpassword';
} }
$uid = $this->addUser($username, $password); $uid = $this->addUser($username, $password, $privateKey);
$req = new HTTP_Request2( $req = new HTTP_Request2(
$GLOBALS['unittestUrl'] . '/login.php?unittestMode=1', $GLOBALS['unittestUrl'] . '/login.php?unittestMode=1',
@ -234,7 +249,7 @@ class TestBaseApi extends TestBase
*/ */
protected function setUnittestConfig($arConfig) protected function setUnittestConfig($arConfig)
{ {
$str = '<' . "?php\r\n"; $str = '<' . "?php\n";
foreach ($arConfig as $name => $value) { foreach ($arConfig as $name => $value) {
$str .= '$' . $name . ' = ' $str .= '$' . $name . ' = '
. var_export($value, true) . ";\n"; . var_export($value, true) . ";\n";

View file

@ -34,6 +34,157 @@ class UserTest extends TestBase
/**
* @covers SemanticScuttle_Service_User::addUser
*/
public function testAddUserPrivateKey()
{
$name = substr(md5(uniqid()), 0, 6);
$pkey = 'my-privateKey';
$id = $this->us->addUser(
$name, uniqid(), 'foo@example.org', $pkey
);
$this->assertNotEquals(false, $id);
$this->assertInternalType('integer', $id);
$arUser = $this->us->getUserByPrivateKey($pkey);
$this->assertNotEquals(false, $arUser, 'user not found by private key');
$this->assertEquals($id, $arUser['uId'], 'wrong user loaded');
}
/**
* @covers SemanticScuttle_Service_User::updateUser
*/
public function testUpdateUserFalseWhenIdNotNumeric()
{
$this->assertFalse(
$this->us->updateUser('foo', null, null, null, null, null)
);
}
/**
* @covers SemanticScuttle_Service_User::updateUser
*/
public function testUpdateUserPrivateKeyNewKeyEnabled()
{
$pkey = 'testUpdateUserPrivateKeyNewKey12';
$uid = $this->addUser();
$this->assertTrue(
$this->us->updateUser(
$uid, 'password', 'name', 'test@example.org', '', '',
$pkey, true
)
);
$arUser = $this->us->getUser($uid);
$this->assertInternalType('array', $arUser);
$this->assertEquals($pkey, $arUser['privateKey']);
}
/**
* @covers SemanticScuttle_Service_User::updateUser
*/
public function testUpdateUserPrivateKeyNewKeyDisabled()
{
$pkey = 'testUpdateUserPrivateKeyNewKeyDi';
$uid = $this->addUser();
$this->assertTrue(
$this->us->updateUser(
$uid, 'password', 'name', 'test@example.org', '', '',
$pkey, false
)
);
$arUser = $this->us->getUser($uid);
$this->assertInternalType('array', $arUser);
$this->assertEquals(
'-' . $pkey, $arUser['privateKey'],
'private key did not get disabled'
);
}
/**
* Passing an empty string / NULL as key but enabling it
* should automatically create a new key.
*
* @covers SemanticScuttle_Service_User::updateUser
*/
public function testUpdateUserPrivateKeyNoKeyEnabled()
{
$pkey = 'testUpdateUserPrivateKeyNoKeyEna';
$uid = $this->addUser();
$this->assertTrue(
$this->us->updateUser(
$uid, 'password', 'name', 'test@example.org', '', '',
null, true
)
);
$arUser = $this->us->getUser($uid);
$this->assertInternalType('array', $arUser);
$this->assertNotEquals(
'', $arUser['privateKey'], 'private key was not created'
);
}
/**
* Passing an empty string / NULL as key and disabling it
* should keep no key
*
* @covers SemanticScuttle_Service_User::updateUser
*/
public function testUpdateUserPrivateKeyNoKeyDisabled()
{
$pkey = 'testUpdateUserPrivateKeyNoKeyDis';
$uid = $this->addUser();
$this->assertTrue(
$this->us->updateUser(
$uid, 'password', 'name', 'test@example.org', '', '',
null, false
)
);
$arUser = $this->us->getUser($uid);
$this->assertInternalType('array', $arUser);
$this->assertEquals(
'', $arUser['privateKey'], 'private key was set'
);
}
/**
* Passing an empty string / NULL as key and disabling it
* should keep no key
*
* @covers SemanticScuttle_Service_User::updateUser
*/
public function testUpdateUserPrivateKeyExistingKeyEnabled()
{
$pkey = '12345678901234567890123456789012';
$uid = $this->addUser();
$this->assertTrue(
$this->us->updateUser(
$uid, 'password', 'name', 'test@example.org', '', '',
'-' . $pkey, true
)
);
$arUser = $this->us->getUser($uid);
$this->assertInternalType('array', $arUser);
$this->assertEquals(
$pkey, $arUser['privateKey'], 'private key was not enabled'
);
}
//FIXME: verify I cannot re-use private key of different user
/** /**
* Test that setting the current user ID is permanent. * Test that setting the current user ID is permanent.
* and that the current user array is the same ID * and that the current user array is the same ID
@ -176,5 +327,185 @@ class UserTest extends TestBase
); );
} }
public function testGetUserByPrivateKeyEmptyKey()
{
$arUser = $this->us->getUserByPrivateKey(null);
$this->assertFalse($arUser);
}
public function testGetUserByPrivateKeyInvalid()
{
$arUser = $this->us->getUserByPrivateKey('foobar');
$this->assertFalse($arUser);
$arUser = $this->us->getUserByPrivateKey('%');
$this->assertFalse($arUser);
}
public function testGetUserByPrivateKeyValidKey()
{
$pkey = $this->us->getNewPrivateKey();
$uId = $this->addUser(null, null, $pkey);
$arUser = $this->us->getUserByPrivateKey($pkey);
$this->assertInternalType('array', $arUser);
$this->assertArrayHasKey('uId', $arUser);
$this->assertArrayHasKey('username', $arUser);
$this->assertEquals($uId, $arUser['uId']);
}
/**
* @covers SemanticScuttle_Service_User::privateKeyExists
*/
public function testPrivateKeyExistsEmpty()
{
$this->assertFalse($this->us->privateKeyExists(null));
$this->assertFalse($this->us->privateKeyExists(''));
}
/**
* @covers SemanticScuttle_Service_User::privateKeyExists
*/
public function testPrivateKeyExistsInvalid()
{
$this->assertFalse($this->us->privateKeyExists('-1'));
}
/**
* @covers SemanticScuttle_Service_User::privateKeyExists
*/
public function testPrivateKeyExists()
{
$randKey = $this->us->getNewPrivateKey();
$this->assertFalse($this->us->privateKeyExists($randKey));
$uid = $this->addUser(null, null, $randKey);
$this->us->setCurrentUserId($uid);
$this->assertEquals($uid, $this->us->getCurrentUserId());
$this->assertTrue($this->us->privateKeyExists($randKey));
}
/**
* @covers SemanticScuttle_Service_User::isPrivateKeyValid
*/
public function testIsPrivateKeyValid()
{
$this->assertFalse(
$this->us->isPrivateKeyValid(null),
'NULL is an invalid private key'
);
$randKey = $this->us->getNewPrivateKey();
$this->assertTrue(
$this->us->isPrivateKeyValid($randKey),
'generated key should be valid'
);
$randKey2 = '-'.$this->us->getNewPrivateKey();
$this->assertFalse(
$this->us->isPrivateKeyValid($randKey2),
'disabled privateKey should return false'
);
}
public function testLoginPrivateKeyInvalid()
{
/* normal user with enabled privateKey */
$randKey = $this->us->getNewPrivateKey();
$uid1 = $this->addUser('testusername', 'passw0rd', $randKey);
/* user that has disabled privateKey */
$randKey2 = '-'.$this->us->getNewPrivateKey();
$uid2 = $this->addUser('seconduser', 'passw0RD', $randKey2);
/* test invalid private key */
$this->assertFalse(
$this->us->loginPrivateKey('02848248084082408240824802408248')
);
}
public function testLoginPrivateKeyValidEnabledKey()
{
/* normal user with enabled privateKey */
$randKey = $this->us->getNewPrivateKey();
$uid1 = $this->addUser('testusername', 'passw0rd', $randKey);
/* user that has disabled privateKey */
$randKey2 = '-'.$this->us->getNewPrivateKey();
$uid2 = $this->addUser('seconduser', 'passw0RD', $randKey2);
/* test valid credentials with private key enabled */
$this->assertTrue(
$this->us->loginPrivateKey($randKey)
);
}
public function testLoginPrivateKeyInvalidEnabledKey()
{
/* normal user with enabled privateKey */
$randKey = $this->us->getNewPrivateKey();
$uid1 = $this->addUser('testusername', 'passw0rd', $randKey);
/* user that has disabled privateKey */
$randKey2 = '-'.$this->us->getNewPrivateKey();
$uid2 = $this->addUser('seconduser', 'passw0RD', $randKey2);
/* test valid credentials with private key enabled but invalid key */
$this->assertFalse(
$this->us->loginPrivateKey('123')
);
}
public function testLoginPrivateKeyValidDisabledKey()
{
/* normal user with enabled privateKey */
$randKey = $this->us->getNewPrivateKey();
$uid1 = $this->addUser('testusername', 'passw0rd', $randKey);
/* user that has disabled privateKey */
$randKey2 = '-'.$this->us->getNewPrivateKey();
$uid2 = $this->addUser('seconduser', 'passw0RD', $randKey2);
/* confirm user exists so future fails should be due to randkey */
$this->assertTrue(
$this->us->login('seconduser', 'passw0RD', false)
);
/* test valid credentials with private key disabled */
$this->assertFalse(
$this->us->loginPrivateKey($randKey2)
);
}
public function testLoginPrivateKeyInvalidDisabled()
{
/* normal user with enabled privateKey */
$randKey = $this->us->getNewPrivateKey();
$uid1 = $this->addUser('testusername', 'passw0rd', $randKey);
/* user that has disabled privateKey */
$randKey2 = '-'.$this->us->getNewPrivateKey();
$uid2 = $this->addUser('seconduser', 'passw0RD', $randKey2);
/* test valid credentials with private key disabled and invalid key */
$this->assertFalse(
$this->us->loginPrivateKey('-1')
);
$this->assertFalse(
$this->us->loginPrivateKey(null)
);
}
} }
?> ?>

View file

@ -4,7 +4,7 @@ require_once 'HTTP/Request2.php';
class www_bookmarksTest extends TestBaseApi class www_bookmarksTest extends TestBaseApi
{ {
protected $urlPart = 'api/posts/add'; protected $urlPart = 'bookmarks.php';
/** /**
* Test that the default privacy setting is selected in the Privacy * Test that the default privacy setting is selected in the Privacy
@ -16,23 +16,11 @@ class www_bookmarksTest extends TestBaseApi
$this->setUnittestConfig( $this->setUnittestConfig(
array('defaults' => array('privacy' => 2)) array('defaults' => array('privacy' => 2))
); );
list($req, $uId) = $this->getLoggedInRequest(); list($req, $uId) = $this->getLoggedInRequest();
$cookies = $req->getCookieJar();
$req->setMethod(HTTP_Request2::METHOD_POST);
$req->addPostParameter('url', 'http://www.example.org/testdefaultprivacyposts_bookmarksget');
$req->addPostParameter('description', 'Test bookmark 1 for default privacy.');
$req->addPostParameter('status', '0');
$req->send();
$bms = $this->bs->getBookmarks(0, null, $uId);
$this->assertEquals(1, count($bms['bookmarks']));
$user = $this->us->getUser($uId); $user = $this->us->getUser($uId);
$reqUrl = $GLOBALS['unittestUrl'] . 'bookmarks.php/' . $user['username'] . '?action=get' . '&unittestMode=1';
list($req, $uId) = $this->getAuthRequest('?unittestMode=1');
$req->setMethod(HTTP_Request2::METHOD_POST); $req->setMethod(HTTP_Request2::METHOD_POST);
$req->setUrl($reqUrl); $req->setUrl($this->getTestUrl('/' . $user['username'] . '?action=get'));
$req->setCookieJar($cookies);
$req->addPostParameter('submitted', '1'); $req->addPostParameter('submitted', '1');
$response = $req->send(); $response = $req->send();
$response_body = $response->getBody(); $response_body = $response->getBody();
@ -41,12 +29,15 @@ class www_bookmarksTest extends TestBaseApi
$ns = $x->getDocNamespaces(); $ns = $x->getDocNamespaces();
$x->registerXPathNamespace('ns', reset($ns)); $x->registerXPathNamespace('ns', reset($ns));
$elements = $x->xpath('//ns:select[@name="status"]/ns:option[@selected="selected"]'); $elements = $x->xpath(
'//ns:select[@name="status"]/ns:option[@selected="selected"]'
);
$this->assertEquals(1, count($elements), 'No selected status option found'); $this->assertEquals(1, count($elements), 'No selected status option found');
$this->assertEquals(2, (string)$elements[0]['value']); $this->assertEquals(2, (string)$elements[0]['value']);
}//end testDefaultPrivacyBookmarksAddMissingTitleMissingPrivacy }//end testDefaultPrivacyBookmarksAddMissingTitleMissingPrivacy
/** /**
* Test that the default privacy setting is selected in the Privacy * Test that the default privacy setting is selected in the Privacy
* drop-down list when a new bookmark is being created. * drop-down list when a new bookmark is being created.
@ -56,13 +47,10 @@ class www_bookmarksTest extends TestBaseApi
$this->setUnittestConfig( $this->setUnittestConfig(
array('defaults' => array('privacy' => 1)) array('defaults' => array('privacy' => 1))
); );
list($req, $uId) = $this->getLoggedInRequest('?unittestMode=1'); list($req, $uId) = $this->getLoggedInRequest();
$user = $this->us->getUser($uId); $user = $this->us->getUser($uId);
$reqUrl = $GLOBALS['unittestUrl'] . 'bookmarks.php/' $req->setUrl($this->getTestUrl('/' . $user['username'] . '?action=add'));
. $user['username'] . '?action=add' . '&unittestMode=1';
$req->setUrl($reqUrl);
$req->setMethod(HTTP_Request2::METHOD_GET);
$response = $req->send(); $response = $req->send();
$response_body = $response->getBody(); $response_body = $response->getBody();
$this->assertNotEquals('', $response_body, 'Response is empty'); $this->assertNotEquals('', $response_body, 'Response is empty');
@ -71,10 +59,70 @@ class www_bookmarksTest extends TestBaseApi
$ns = $x->getDocNamespaces(); $ns = $x->getDocNamespaces();
$x->registerXPathNamespace('ns', reset($ns)); $x->registerXPathNamespace('ns', reset($ns));
$elements = $x->xpath('//ns:select[@name="status"]/ns:option[@selected="selected"]'); $elements = $x->xpath(
'//ns:select[@name="status"]/ns:option[@selected="selected"]'
);
$this->assertEquals(1, count($elements), 'No selected status option found'); $this->assertEquals(1, count($elements), 'No selected status option found');
$this->assertEquals(1, (string)$elements[0]['value']); $this->assertEquals(1, (string)$elements[0]['value']);
}//end testDefaultPrivacyBookmarksAdd }//end testDefaultPrivacyBookmarksAdd
/**
* Test that the private RSS link exists when a user
* has a private key and is enabled
*/
public function testVerifyPrivateRSSLinkExists()
{
list($req, $uId) = $this->getLoggedInRequest('?unittestMode=1', true, true);
$user = $this->us->getUser($uId);
$req->setUrl($this->getTestUrl('/' . $user['username']));
$response = $req->send();
$response_body = $response->getBody();
$this->assertNotEquals('', $response_body, 'Response is empty');
$x = simplexml_load_string($response_body);
$ns = $x->getDocNamespaces();
$x->registerXPathNamespace('ns', reset($ns));
$elements = $x->xpath(
'//ns:link[@rel="alternate" and @type="application/rss+xml"]'
);
$this->assertEquals(
2, count($elements), 'Number of Links in Head not correct'
);
$this->assertContains('privateKey=', (string)$elements[1]['href']);
}//end testVerifyPrivateRSSLinkExists
/**
* Test that the private RSS link doesn't exists when a user
* does not have a private key or is not enabled
*/
public function testVerifyPrivateRSSLinkDoesNotExist()
{
list($req, $uId) = $this->getLoggedInRequest('?unittestMode=1', true);
$user = $this->us->getUser($uId);
$req->setUrl($this->getTestUrl('/' . $user['username']));
$response = $req->send();
$response_body = $response->getBody();
$this->assertNotEquals('', $response_body, 'Response is empty');
$x = simplexml_load_string($response_body);
$ns = $x->getDocNamespaces();
$x->registerXPathNamespace('ns', reset($ns));
$elements = $x->xpath(
'//ns:link[@rel="alternate" and @type="application/rss+xml"]'
);
$this->assertEquals(
1, count($elements), 'Number of Links in Head not correct'
);
$this->assertNotContains('privateKey=', (string)$elements[0]['href']);
}//end testVerifyPrivateRSSLinkDoesNotExist
}//end class www_bookmarksTest }//end class www_bookmarksTest
?> ?>

58
tests/www/indexTest.php Normal file
View file

@ -0,0 +1,58 @@
<?php
require_once dirname(__FILE__) . '/../prepare.php';
require_once 'HTTP/Request2.php';
class www_indexTest extends TestBaseApi
{
protected $urlPart = '';
/**
* Test that the private rss feed exists when user is setup
* with a private key and is enabled
*/
public function testVerifyPrivateRSSLinkExists()
{
list($req, $uId) = $this->getLoggedInRequest('?unittestMode=1', true, true);
$user = $this->us->getUser($uId);
$response = $req->send();
$response_body = $response->getBody();
$this->assertNotEquals('', $response_body, 'Response is empty');
$x = simplexml_load_string($response_body);
$ns = $x->getDocNamespaces();
$x->registerXPathNamespace('ns', reset($ns));
$elements = $x->xpath('//ns:link[@rel="alternate" and @type="application/rss+xml"]');
$this->assertEquals(2, count($elements), 'Number of Links in Head not correct');
$this->assertContains('privateKey=', (string)$elements[1]['href']);
}//end testVerifyPrivateRSSLinkExists
/**
* Test that the private RSS link doesn't exists when a user
* does not have a private key, or the private key is not enabled
*/
public function testVerifyPrivateRSSLinkDoesNotExist()
{
list($req, $uId) = $this->getLoggedInRequest('?unittestMode=1', true);
$user = $this->us->getUser($uId);
$response = $req->send();
$response_body = $response->getBody();
$this->assertNotEquals('', $response_body, 'Response is empty');
$x = simplexml_load_string($response_body);
$ns = $x->getDocNamespaces();
$x->registerXPathNamespace('ns', reset($ns));
$elements = $x->xpath('//ns:link[@rel="alternate" and @type="application/rss+xml"]');
$this->assertEquals(1, count($elements), 'Number of Links in Head not correct');
$this->assertNotContains('privateKey=', (string)$elements[0]['href']);
}//end testVerifyPrivateRSSLinkDoesNotExist
}//end class www_bookmarksTest
?>

151
tests/www/rssTest.php Normal file
View file

@ -0,0 +1,151 @@
<?php
require_once dirname(__FILE__) . '/../prepare.php';
require_once 'HTTP/Request2.php';
class www_rssTest extends TestBaseApi
{
protected $urlPart = 'rss.php';
/**
* Verifies that the given number of feed items exist in the feed
* XML tree.
*
* @var SimpleXMLElement $simpleXml RSS feed root element
* @var integer $nCount Number of expected feed items
* @var string $msg Error message
*/
protected function assertItemCount(
SimpleXMLElement $simpleXml, $nCount, $msg = null
) {
$this->assertEquals($nCount, count($simpleXml->channel->item), $msg);
}
/**
* A private bookmark should not show up in the global rss feed if the
* user is not logged in nor passes the private key
*/
public function testAllPrivateBookmarkNotLoggedIn()
{
list($uId, $username) = $this->addUserData();
$this->addBookmark(
$uId, null, SemanticScuttle_Model_Bookmark::SPRIVATE
);
$req = $this->getRequest();
$response_body = $req->send()->getBody();
$rss = simplexml_load_string($response_body);
$this->assertItemCount($rss, 0, 'I see a private bookmark');
}
/**
* A private bookmark should not show up in the user's rss feed if the
* user is not logged in nor passes the private key
*/
public function testUserPrivateBookmarkNotLoggedIn()
{
list($uId, $username) = $this->addUserData();
$this->addBookmark(
$uId, null, SemanticScuttle_Model_Bookmark::SPRIVATE
);
$req = $this->getRequest('/' . $username);
$response_body = $req->send()->getBody();
$rss = simplexml_load_string($response_body);
$this->assertItemCount($rss, 0, 'I see a private bookmark');
}
/**
* Test the global feed by passing the private key
*/
public function testAllPrivateBookmarkWithPrivateKey()
{
list($uId, $username, $password, $privateKey) = $this->addUserData(
null, null, true
);
$this->addBookmark(
$uId, null, SemanticScuttle_Model_Bookmark::SPRIVATE,
null, 'private bookmark'
);
$req = $this->getRequest('?privateKey=' . $privateKey);
$response_body = $req->send()->getBody();
$rss = simplexml_load_string($response_body);
$this->assertItemCount($rss, 1, 'I miss the private bookmark');
$this->assertEquals(
'private bookmark', (string)$rss->channel->item[0]->title
);
}
/**
* Test the user feed by passing the private key
*/
public function testUserPrivateBookmarkWithPrivateKey()
{
list($uId, $username, $password, $privateKey) = $this->addUserData(
null, null, true
);
$this->addBookmark(
$uId, null, SemanticScuttle_Model_Bookmark::SPRIVATE,
null, 'private bookmark'
);
$req = $this->getRequest('/' . $username . '?privateKey=' . $privateKey);
$response_body = $req->send()->getBody();
$rss = simplexml_load_string($response_body);
$this->assertItemCount($rss, 1, 'I miss the private bookmark');
$this->assertEquals(
'private bookmark', (string)$rss->channel->item[0]->title
);
}
/**
* Verify that fetching the feed with a private key
* does not keep you logged in
*/
public function testUserPrivateKeyDoesNotKeepLoggedYouIn()
{
list($uId, $username, $password, $privateKey) = $this->addUserData(
null, null, true
);
$this->addBookmark(
$uId, null, SemanticScuttle_Model_Bookmark::SPRIVATE,
null, 'private bookmark'
);
$req = $this->getRequest('/' . $username . '?privateKey=' . $privateKey);
$cookies = $req->setCookieJar()->getCookieJar();
$response_body = $req->send()->getBody();
$rss = simplexml_load_string($response_body);
$items = $rss->channel->item;
$this->assertEquals(1, count($items), 'I miss the private bookmark');
$this->assertEquals('private bookmark', (string)$items[0]->title);
//request the feed again, with the same cookies
$req = $this->getRequest('/' . $username);
$req->setCookieJar($cookies);
$response_body = $req->send()->getBody();
$rss = simplexml_load_string($response_body);
$this->assertItemCount($rss, 0, 'I still see the private bookmark');
}
}//end class www_rssTest
?>

View file

@ -1,4 +1,6 @@
<?php <?php
require_once dirname(__FILE__) . '/../prepare.php';
require_once 'HTTP/Request2.php';
class www_SearchTest extends TestBaseApi class www_SearchTest extends TestBaseApi
{ {

View file

@ -0,0 +1,35 @@
<?php
/**
* Ajax script to retrieve new Private Key
*
* PHP version 5.
*
* @category Bookmarking
* @package SemanticScuttle
* @author Christian Weiske <cweiske@cweiske.de>
* @author Mark Pemberton <mpemberton5@gmail.com>
* @license AGPL http://www.gnu.org/licenses/agpl.html
* @link http://sourceforge.net/projects/semanticscuttle
*/
header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT");
header("Cache-Control: no-cache, must-revalidate");
$httpContentType = 'text/xml';
require_once 'www-header.php';
$us = SemanticScuttle_Service_Factory::get('User');
/* Managing all possible inputs */
isset($_GET['url']) ? define('GET_URL', $_GET['url']): define('GET_URL', '');
echo '<?xml version="1.0" encoding="utf-8"?>';
?>
<response>
<method>
getNewPrivateKey
</method>
<result>
<?php echo $us->getNewPrivateKey(); ?>
</result>
</response>

View file

@ -229,12 +229,14 @@ if ($templatename == 'editbookmark.tpl') {
$tplVars['sidebar_blocks'] = array('watchstatus'); $tplVars['sidebar_blocks'] = array('watchstatus');
if (!$cat) { //user page without tags if (!$cat) { //user page without tags
$rssTitle = "My Bookmarks";
$cat = NULL; $cat = NULL;
$tplVars['currenttag'] = NULL; $tplVars['currenttag'] = NULL;
//$tplVars['sidebar_blocks'][] = 'menu2'; //$tplVars['sidebar_blocks'][] = 'menu2';
$tplVars['sidebar_blocks'][] = 'linked'; $tplVars['sidebar_blocks'][] = 'linked';
$tplVars['sidebar_blocks'][] = 'popular'; $tplVars['sidebar_blocks'][] = 'popular';
} else { //pages with tags } else { //pages with tags
$rssTitle = "Tags" . $catTitle;
$rssCat = '/'. filter($cat, 'url'); $rssCat = '/'. filter($cat, 'url');
$tplVars['currenttag'] = $cat; $tplVars['currenttag'] = $cat;
$tplVars['sidebar_blocks'][] = 'tagactions'; $tplVars['sidebar_blocks'][] = 'tagactions';
@ -264,9 +266,32 @@ if ($templatename == 'editbookmark.tpl') {
// Set template vars // Set template vars
$tplVars['rsschannels'] = array( $tplVars['rsschannels'] = array(
array(filter($sitename .': '. $pagetitle), createURL('rss', filter($user, 'url') . $rssCat.'?sort='.getSortOrder())) array(
sprintf(T_('%s: %s'), $sitename, $rssTitle),
createURL('rss', filter($user, 'url'))
. $rssCat . '?sort='.getSortOrder()
)
); );
if ($userservice->isLoggedOn()) {
$currentUsername = $currentUser->getUsername();
if ($userservice->isPrivateKeyValid($currentUser->getPrivateKey())) {
array_push(
$tplVars['rsschannels'],
array(
sprintf(
T_('%s: %s (+private %s)'),
$sitename, $rssTitle, $currentUsername
),
createURL('rss', filter($currentUsername, 'url'))
. $rssCat
. '?sort=' . getSortOrder()
. '&privateKey=' . $currentUser->getPrivateKey()
)
);
}
}
$tplVars['page'] = $page; $tplVars['page'] = $page;
$tplVars['start'] = $start; $tplVars['start'] = $start;
$tplVars['bookmarkCount'] = $start + 1; $tplVars['bookmarkCount'] = $start + 1;

View file

@ -42,9 +42,30 @@ if (GET_ACTION == "logout") {
// Header variables // Header variables
$tplVars['loadjs'] = true; $tplVars['loadjs'] = true;
$tplVars['rsschannels'] = array( $tplVars['rsschannels'] = array(
array(sprintf(T_('%s: Recent bookmarks'), $sitename), createURL('rss').'?sort='.getSortOrder()) array(
sprintf(T_('%s: Recent bookmarks'), $sitename),
createURL('rss') . '?sort=' . getSortOrder()
)
); );
if ($userservice->isLoggedOn()) {
if ($userservice->isPrivateKeyValid($currentUser->getPrivateKey())) {
$currentUsername = $currentUser->getUsername();
array_push(
$tplVars['rsschannels'],
array(
sprintf(
T_('%s: Recent bookmarks (+private %s)'),
$sitename, $currentUsername
),
createURL('rss')
. '?sort=' . getSortOrder()
. '&privateKey=' . $currentUser->getPrivateKey()
)
);
}
}
if ($usecache) { if ($usecache) {
// Generate hash for caching on // Generate hash for caching on
$hashtext = $_SERVER['REQUEST_URI']; $hashtext = $_SERVER['REQUEST_URI'];

View file

@ -1,72 +0,0 @@
/*
Copyright (c) 2004-2008, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
/* SemanticScuttle: This script is a light modification of dojox.form.MultiComboBox
This fork allows specific use until DOJO 1.2.3 in Google CDN. */
if(!dojo._hasResource["js.MultiComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["js.MultiComboBox"] = true;
dojo.provide("js.MultiComboBox");
dojo.experimental("js.MultiComboBox");
dojo.require("dijit.form.ComboBox");
dojo.require("dijit.form.ValidationTextBox");
dojo.declare("js.MultiComboBox",
[dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],{
//
// summary: A ComboBox that accpets multiple inputs on a single line?
//
// delimiter: String
// The character to use to separate items in the ComboBox input
delimiter: ",",
_previousMatches: false,
_setValueAttr: function(value){
if (this.delimiter && value.length != 0){
value = value+this.delimiter+" ";
arguments[0] = this._addPreviousMatches(value);
}
this.inherited(arguments);
},
_addPreviousMatches: function(/* String */text){
if(this._previousMatches){
if(!text.match(new RegExp("^"+this._previousMatches))){
text = this._previousMatches+text;
}
}
text = this._cleanupDelimiters(text); // SScuttle: this line was moved
return text; // String
},
_cleanupDelimiters: function(/* String */text){
if(this.delimiter){
text = text.replace(new RegExp(" +"), " ");
text = text.replace(new RegExp("^ *"+this.delimiter+"* *"), "");
text = text.replace(new RegExp(this.delimiter+" *"+this.delimiter), this.delimiter);
}
return text;
},
_autoCompleteText: function(/* String */text){
arguments[0] = this._addPreviousMatches(text);
this.inherited(arguments);
},
_startSearch: function(/* String */text){
text = this._cleanupDelimiters(text);
var re = new RegExp("^.*"+this.delimiter+" *");
if((this._previousMatches = text.match(re))){
arguments[0] = text.replace(re, "");
}
this.inherited(arguments);
}
});
}

View file

@ -90,6 +90,24 @@ function useAddress(ele) {
} }
} }
/**
* Makes an ajax call to PHP script to generate an new Private Key
*
* @param input Calling object
* @param response Response object that returned value is placed
*
* @return boolean Returns false to halt execution after call
*/
function getNewPrivateKey(input, response){
var pk = document.getElementById('pPrivateKey');
if (response != null) {
pk.value = response.trim();
} else {
loadXMLDocProc('<?php echo ROOT; ?>ajaxGetNewPrivateKey.php');
}
return false;
}
function getTitle(input, response){ function getTitle(input, response){
var title = document.getElementById('titleField'); var title = document.getElementById('titleField');
if (title.value == '') { if (title.value == '') {

View file

@ -23,12 +23,16 @@ require_once 'www-header.php';
/* Service creation: only useful services are created */ /* Service creation: only useful services are created */
// No specific services // No specific services
$tplVars['loadjs'] = true;
/* Managing all possible inputs */ /* Managing all possible inputs */
isset($_POST['submittedPK']) ? define('POST_SUBMITTEDPK', $_POST['submittedPK']): define('POST_SUBMITTEDPK', '');
isset($_POST['submitted']) ? define('POST_SUBMITTED', $_POST['submitted']): define('POST_SUBMITTED', ''); isset($_POST['submitted']) ? define('POST_SUBMITTED', $_POST['submitted']): define('POST_SUBMITTED', '');
isset($_POST['pPass']) ? define('POST_PASS', $_POST['pPass']): define('POST_PASS', ''); isset($_POST['pPass']) ? define('POST_PASS', $_POST['pPass']): define('POST_PASS', '');
isset($_POST['pPassConf']) ? define('POST_PASSCONF', $_POST['pPassConf']): define('POST_PASSCONF', ''); isset($_POST['pPassConf']) ? define('POST_PASSCONF', $_POST['pPassConf']): define('POST_PASSCONF', '');
isset($_POST['pName']) ? define('POST_NAME', $_POST['pName']): define('POST_NAME', ''); isset($_POST['pName']) ? define('POST_NAME', $_POST['pName']): define('POST_NAME', '');
isset($_POST['pPrivateKey']) ? define('POST_PRIVATEKEY', $_POST['pPrivateKey']): define('POST_PRIVATEKEY', '');
isset($_POST['pEnablePrivateKey']) ? define('POST_ENABLEPRIVATEKEY', $_POST['pEnablePrivateKey']): define('POST_ENABLEPRIVATEKEY', '');
isset($_POST['pMail']) ? define('POST_MAIL', $_POST['pMail']): define('POST_MAIL', ''); isset($_POST['pMail']) ? define('POST_MAIL', $_POST['pMail']): define('POST_MAIL', '');
isset($_POST['pPage']) ? define('POST_PAGE', $_POST['pPage']): define('POST_PAGE', ''); isset($_POST['pPage']) ? define('POST_PAGE', $_POST['pPage']): define('POST_PAGE', '');
isset($_POST['pDesc']) ? define('POST_DESC', $_POST['pDesc']): define('POST_DESC', ''); isset($_POST['pDesc']) ? define('POST_DESC', $_POST['pDesc']): define('POST_DESC', '');
@ -61,10 +65,19 @@ if ($user) {
exit(); exit();
} }
$tplVars['privateKeyIsEnabled'] = '';
if ($userservice->isLoggedOn() && $user == $currentUser->getUsername()) { if ($userservice->isLoggedOn() && $user == $currentUser->getUsername()) {
$title = T_('My Profile'); $title = T_('My Profile');
$tplVars['privateKey'] = $currentUser->getPrivateKey(true);
if ($userservice->isPrivateKeyValid($currentUser->getPrivateKey())) {
$tplVars['privateKeyIsEnabled'] = 'checked="checked"';
} else {
$tplVars['privateKeyIsEnabled'] = '';
}
} else { } else {
$title = T_('Profile') .': '. $user; $title = T_('Profile') .': '. $user;
$tplVars['privateKey'] = '';
} }
$tplVars['pagetitle'] = $title; $tplVars['pagetitle'] = $title;
$tplVars['subtitle'] = $title; $tplVars['subtitle'] = $title;
@ -72,11 +85,19 @@ $tplVars['subtitle'] = $title;
$tplVars['user'] = $user; $tplVars['user'] = $user;
$tplVars['userid'] = $userid; $tplVars['userid'] = $userid;
/* Update Private Key */
if (POST_SUBMITTEDPK!='' && $currentUser->getId() == $userid) {
$userinfo = $userservice->getObjectUserByUsername($user);
$tplVars['privateKey'] = $userservice->getNewPrivateKey();
}
if (POST_SUBMITTED!='' && $currentUser->getId() == $userid) { if (POST_SUBMITTED!='' && $currentUser->getId() == $userid) {
$error = false; $error = false;
$detPass = trim(POST_PASS); $detPass = trim(POST_PASS);
$detPassConf = trim(POST_PASSCONF); $detPassConf = trim(POST_PASSCONF);
$detName = trim(POST_NAME); $detName = trim(POST_NAME);
$detPrivateKey = trim(POST_PRIVATEKEY);
$detEnablePrivateKey = trim(POST_ENABLEPRIVATEKEY);
$detMail = trim(POST_MAIL); $detMail = trim(POST_MAIL);
$detPage = trim(POST_PAGE); $detPage = trim(POST_PAGE);
$detDesc = filter(POST_DESC); $detDesc = filter(POST_DESC);
@ -102,13 +123,19 @@ if (POST_SUBMITTED!='' && $currentUser->getId() == $userid) {
$tplVars['error'] = T_('E-mail address is not valid.'); $tplVars['error'] = T_('E-mail address is not valid.');
} }
if (!$error) { if (!$error) {
if (!$userservice->updateUser($userid, $detPass, $detName, $detMail, $detPage, $detDesc)) { if (!$userservice->updateUser($userid, $detPass, $detName, $detMail, $detPage, $detDesc, $detPrivateKey, $detEnablePrivateKey)) {
$tplVars['error'] = T_('An error occurred while saving your changes.'); $tplVars['error'] = T_('An error occurred while saving your changes.');
} else { } else {
$tplVars['msg'] = T_('Changes saved.'); $tplVars['msg'] = T_('Changes saved.');
} }
} }
$userinfo = $userservice->getObjectUserByUsername($user); $userinfo = $userservice->getObjectUserByUsername($user);
$tplVars['privateKey'] = $userinfo->getPrivateKey(true);
if ($userservice->isPrivateKeyValid($userinfo->getPrivateKey())) {
$tplVars['privateKeyIsEnabled'] = 'checked="checked"';
} else {
$tplVars['privateKeyIsEnabled'] = '';
}
} }
if (!$userservice->isLoggedOn() || $currentUser->getId() != $userid) { if (!$userservice->isLoggedOn() || $currentUser->getId() != $userid) {

View file

@ -64,7 +64,12 @@ if (!isset($rssEntries) || $rssEntries <= 0) {
$rssEntries = $maxRssEntries; $rssEntries = $maxRssEntries;
} }
$privateKey = null;
if (isset($_GET['privateKey'])) {
$privateKey = $_GET['privateKey'];
}
$userid = null;
$watchlist = null; $watchlist = null;
$pagetitle = ''; $pagetitle = '';
if ($user && $user != 'all') { if ($user && $user != 'all') {
@ -78,8 +83,22 @@ if ($user && $user != 'all') {
} else { } else {
if ($userinfo = $userservice->getUserByUsername($user)) { if ($userinfo = $userservice->getUserByUsername($user)) {
$userid =& $userinfo[$userservice->getFieldName('primary')]; $userid =& $userinfo[$userservice->getFieldName('primary')];
/* if user is not logged in and has valid privateKey */
if (!$userservice->isLoggedOn()) {
if ($privateKey != null) {
if (!$userservice->loginPrivateKey($privateKey)) {
$tplVars['error'] = sprintf(T_('Failed to Autenticate User with username %s using private key'), $user);
header('Content-type: text/html; charset=utf-8');
$templateservice->loadTemplate('error.404.tpl', $tplVars);
//throw a 404 error
exit();
}
}
}
} else { } else {
$tplVars['error'] = sprintf(T_('User with username %s was not found'), $user); $tplVars['error'] = sprintf(T_('User with username %s was not found'), $user);
header('Content-type: text/html; charset=utf-8');
$templateservice->loadTemplate('error.404.tpl', $tplVars); $templateservice->loadTemplate('error.404.tpl', $tplVars);
//throw a 404 error //throw a 404 error
exit(); exit();
@ -87,7 +106,17 @@ if ($user && $user != 'all') {
} }
$pagetitle .= ": ". $user; $pagetitle .= ": ". $user;
} else { } else {
if ($privateKey != null) {
if (!$userservice->loginPrivateKey($privateKey)) {
$tplVars['error'] = sprintf(T_('Failed to Autenticate User with username %s using private key'), $user);
header('Content-type: text/html; charset=utf-8');
$templateservice->loadTemplate('error.404.tpl', $tplVars);
//throw a 404 error
exit();
}
} else {
$userid = null; $userid = null;
}
} }
if ($cat) { if ($cat) {
@ -100,7 +129,8 @@ $tplVars['feeddescription'] = sprintf(T_('Recent bookmarks posted to %s'), $GLOB
$bookmarks = $bookmarkservice->getBookmarks( $bookmarks = $bookmarkservice->getBookmarks(
0, $rssEntries, $userid, $cat, 0, $rssEntries, $userid, $cat,
null, getSortOrder(), $watchlist null, getSortOrder(), $watchlist,
null, null, null
); );
$bookmarks_tmp = filter($bookmarks['bookmarks']); $bookmarks_tmp = filter($bookmarks['bookmarks']);

View file

@ -67,9 +67,31 @@ if ($usecache) {
$tplVars['pagetitle'] = T_('Tags') .': '. $cat; $tplVars['pagetitle'] = T_('Tags') .': '. $cat;
$tplVars['loadjs'] = true; $tplVars['loadjs'] = true;
$tplVars['rsschannels'] = array( $tplVars['rsschannels'] = array(
array(filter($sitename .': '. $pagetitle), createURL('rss', 'all/'. filter($cat, 'url')).'?sort='.getSortOrder()) array(
sprintf(T_('%s: tagged with "%s"'), $sitename, $cat),
createURL('rss', 'all/' . filter($cat, 'url'))
. '?sort='.getSortOrder()
)
); );
if ($userservice->isLoggedOn()) {
if ($userservice->isPrivateKeyValid($currentUser->getPrivateKey())) {
$currentUsername = $currentUser->getUsername();
array_push(
$tplVars['rsschannels'],
array(
sprintf(
T_('%s: tagged with "%s" (+private %s)'),
$sitename, $cat, $currentUsername
),
createURL('rss', filter($currentUsername, 'url'))
. '?sort=' . getSortOrder()
. '&privateKey=' . $currentUser->getPrivateKey()
)
);
}
}
// Pagination // Pagination
$perpage = getPerPageCount($currentUser); $perpage = getPerPageCount($currentUser);
if (intval(GET_PAGE) > 1) { if (intval(GET_PAGE) > 1) {